mirror of
https://github.com/bulletphysics/bullet3
synced 2024-12-14 13:50:04 +00:00
951 lines
36 KiB
C++
951 lines
36 KiB
C++
/*
|
|
Bullet Continuous Collision Detection and Physics Library
|
|
Copyright (c) 2015 Google Inc. http://bulletphysics.org
|
|
|
|
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.
|
|
*/
|
|
|
|
#ifndef NN3D_WALKERS_TIME_WARP_BASE_H
|
|
#define NN3D_WALKERS_TIME_WARP_BASE_H
|
|
|
|
#include "btBulletDynamicsCommon.h"
|
|
#include "LinearMath/btVector3.h"
|
|
#include "LinearMath/btAlignedObjectArray.h"
|
|
#include "LinearMath/btQuickprof.h" // Use your own timer, this timer is only used as we lack another timer
|
|
|
|
#include "../CommonInterfaces/CommonRigidBodyBase.h"
|
|
#include "../CommonInterfaces/CommonParameterInterface.h"
|
|
|
|
//Solvers
|
|
#include "BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolver.h"
|
|
#include "BulletDynamics/ConstraintSolver/btNNCGConstraintSolver.h"
|
|
#include "BulletDynamics/Featherstone/btMultiBodyConstraintSolver.h"
|
|
#include "BulletDynamics/Featherstone/btMultiBodyDynamicsWorld.h"
|
|
#include "BulletDynamics/MLCPSolvers/btDantzigSolver.h"
|
|
#include "BulletDynamics/MLCPSolvers/btSolveProjectedGaussSeidel.h"
|
|
#include "BulletDynamics/MLCPSolvers/btLemkeSolver.h"
|
|
#include "BulletDynamics/MLCPSolvers/btMLCPSolver.h"
|
|
|
|
#include "../Utils/b3ERPCFMHelper.hpp" // ERP/CFM setting utils
|
|
|
|
static btScalar gSimulationSpeed = 1; // default simulation speed at startup
|
|
|
|
// the current simulation speeds to choose from (the slider will snap to those using a custom form of snapping)
|
|
namespace SimulationSpeeds
|
|
{
|
|
static double /*0*/ PAUSE = 0;
|
|
static double /*1*/ QUARTER_SPEED = 0.25;
|
|
static double /*2*/ HALF_SPEED = 0.5;
|
|
static double /*3*/ NORMAL_SPEED = 1;
|
|
static double /*4*/ DOUBLE_SPEED = 2;
|
|
static double /*5*/ QUADRUPLE_SPEED = 4;
|
|
static double /*6*/ DECUPLE_SPEED = 10;
|
|
static double /*7*/ CENTUPLE_SPEED = 100;
|
|
static double /*8*/ QUINCENTUPLE_SPEED = 500;
|
|
static double /*9*/ MILLITUPLE_SPEED = 1000;
|
|
static double /*0*/ MAX_SPEED = MILLITUPLE_SPEED;
|
|
static double /**/ NUM_SPEEDS = 10;
|
|
}; // namespace SimulationSpeeds
|
|
|
|
// add speeds from the namespace here
|
|
static double speeds[] = {
|
|
SimulationSpeeds::PAUSE,
|
|
SimulationSpeeds::QUARTER_SPEED, SimulationSpeeds::HALF_SPEED,
|
|
SimulationSpeeds::NORMAL_SPEED, SimulationSpeeds::DOUBLE_SPEED,
|
|
SimulationSpeeds::QUADRUPLE_SPEED, SimulationSpeeds::DECUPLE_SPEED,
|
|
SimulationSpeeds::CENTUPLE_SPEED, SimulationSpeeds::QUINCENTUPLE_SPEED,
|
|
SimulationSpeeds::MILLITUPLE_SPEED};
|
|
|
|
static btScalar gSolverIterations = 10; // default number of solver iterations for the iterative solvers
|
|
|
|
static bool gIsHeadless = false; // demo runs with graphics by default
|
|
|
|
static bool gChangeErpCfm = false; // flag to make recalculation of ERP/CFM
|
|
|
|
static int gMinSpeed = SimulationSpeeds::PAUSE; // the minimum simulation speed
|
|
|
|
static int gMaxSpeed = SimulationSpeeds::MAX_SPEED; // the maximum simulation speed
|
|
|
|
static bool gMaximumSpeed = false; // the demo does not try to achieve maximum stepping speed by default
|
|
|
|
static bool gInterpolate = false; // the demo does not use any bullet interpolated physics substeps
|
|
|
|
static bool useSplitImpulse = true; // split impulse fixes issues with restitution in Baumgarte stabilization
|
|
// http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=9&t=7117&p=24631&hilit=Baumgarte#p24631
|
|
// disabling continuous collision detection can also fix issues with restitution, though CCD is disabled by default an only kicks in at higher speeds
|
|
// set CCD speed threshold and testing sphere radius per rigidbody (rb->setCCDSpeedThreshold())
|
|
|
|
// all supported solvers by bullet
|
|
enum SolverEnumType
|
|
{
|
|
SEQUENTIALIMPULSESOLVER = 0,
|
|
GAUSSSEIDELSOLVER = 1,
|
|
NNCGSOLVER = 2,
|
|
DANZIGSOLVER = 3,
|
|
LEMKESOLVER = 4,
|
|
|
|
NUM_SOLVERS = 6
|
|
};
|
|
|
|
// solvers can be changed by drop down menu
|
|
namespace SolverType
|
|
{
|
|
static char SEQUENTIALIMPULSESOLVER[] = "Sequential Impulse Solver";
|
|
static char GAUSSSEIDELSOLVER[] = "Gauss-Seidel Solver";
|
|
static char NNCGSOLVER[] = "NNCG Solver";
|
|
static char DANZIGSOLVER[] = "Danzig Solver";
|
|
static char LEMKESOLVER[] = "Lemke Solver";
|
|
|
|
}; // namespace SolverType
|
|
|
|
static const char* solverTypes[NUM_SOLVERS];
|
|
|
|
static SolverEnumType SOLVER_TYPE = SEQUENTIALIMPULSESOLVER; // You can switch the solver here
|
|
|
|
//TODO:s===
|
|
//TODO: Give specific explanations about solver values
|
|
|
|
/**
|
|
* Step size of the bullet physics simulator (solverAccuracy). Accuracy versus speed.
|
|
*/
|
|
// Choose an appropriate number of steps per second for your needs
|
|
static btScalar gPhysicsStepsPerSecond = 60.0f; // Default number of steps
|
|
//static btScalar gPhysicsStepsPerSecond = 120.0f; // Double steps for more accuracy
|
|
//static btScalar gPhysicsStepsPerSecond = 240.0f; // For high accuracy
|
|
//static btScalar gPhysicsStepsPerSecond = 1000.0f; // Very high accuracy
|
|
|
|
// appropriate inverses for seconds and milliseconds
|
|
static double fixedPhysicsStepSizeSec = 1.0f / gPhysicsStepsPerSecond; // steps size in seconds
|
|
static double fixedPhysicsStepSizeMilli = 1000.0f / gPhysicsStepsPerSecond; // step size in milliseconds
|
|
|
|
static btScalar gApplicationFrequency = 60.0f; // number of internal application ticks per second
|
|
static int gApplicationTick = 1000.0f / gApplicationFrequency; //ms
|
|
|
|
static btScalar gFramesPerSecond = 30.0f; // number of frames per second
|
|
|
|
static btScalar gERPSpringK = 10;
|
|
static btScalar gERPDamperC = 1;
|
|
|
|
static btScalar gCFMSpringK = 10;
|
|
static btScalar gCFMDamperC = 1;
|
|
static btScalar gCFMSingularityAvoidance = 0;
|
|
|
|
//GUI related parameter changing helpers
|
|
|
|
inline void twxChangePhysicsStepsPerSecond(float physicsStepsPerSecond, void*)
|
|
{ // function to change simulation physics steps per second
|
|
gPhysicsStepsPerSecond = physicsStepsPerSecond;
|
|
}
|
|
|
|
inline void twxChangeFPS(float framesPerSecond, void*)
|
|
{
|
|
gFramesPerSecond = framesPerSecond;
|
|
}
|
|
|
|
inline void twxChangeERPCFM(float notUsed, void*)
|
|
{ // function to change ERP/CFM appropriately
|
|
gChangeErpCfm = true;
|
|
}
|
|
|
|
inline void changeSolver(int comboboxId, const char* item, void* userPointer)
|
|
{ // function to change the solver
|
|
for (int i = 0; i < NUM_SOLVERS; i++)
|
|
{
|
|
if (strcmp(solverTypes[i], item) == 0)
|
|
{ // if the strings are equal
|
|
SOLVER_TYPE = ((SolverEnumType)i);
|
|
b3Printf("=%s=\n Reset the simulation by double clicking it in the menu list.", item);
|
|
return;
|
|
}
|
|
}
|
|
b3Printf("No Change");
|
|
}
|
|
|
|
inline void twxChangeSolverIterations(float notUsed, void* userPtr)
|
|
{ // change the solver iterations
|
|
}
|
|
|
|
inline void clampToCustomSpeedNotches(float speed, void*)
|
|
{ // function to clamp to custom speed notches
|
|
double minSpeed = 0;
|
|
double minSpeedDist = SimulationSpeeds::MAX_SPEED;
|
|
for (int i = 0; i < SimulationSpeeds::NUM_SPEEDS; i++)
|
|
{
|
|
double speedDist = (speeds[i] - speed >= 0) ? speeds[i] - speed : speed - speeds[i]; // float absolute
|
|
|
|
if (minSpeedDist > speedDist)
|
|
{
|
|
minSpeedDist = speedDist;
|
|
minSpeed = speeds[i];
|
|
}
|
|
}
|
|
gSimulationSpeed = minSpeed;
|
|
}
|
|
|
|
inline void switchInterpolated(int buttonId, bool buttonState, void* userPointer)
|
|
{ // toggle if interpolation steps are taken
|
|
gInterpolate = !gInterpolate;
|
|
// b3Printf("Interpolate substeps %s", gInterpolate?"on":"off");
|
|
}
|
|
|
|
inline void switchHeadless(int buttonId, bool buttonState, void* userPointer)
|
|
{ // toggle if the demo should run headless
|
|
gIsHeadless = !gIsHeadless;
|
|
// b3Printf("Run headless %s", gIsHeadless?"on":"off");
|
|
}
|
|
|
|
inline void switchMaximumSpeed(int buttonId, bool buttonState, void* userPointer)
|
|
{ // toggle it the demo should run as fast as possible
|
|
// b3Printf("Run maximum speed %s", gMaximumSpeed?"on":"off");
|
|
}
|
|
|
|
inline void setApplicationTick(float frequency, void*)
|
|
{ // set internal application tick
|
|
gApplicationTick = 1000.0f / frequency;
|
|
}
|
|
|
|
/**
|
|
* @link: Gaffer on Games - Fix your timestep: http://gafferongames.com/game-physics/fix-your-timestep/
|
|
*/
|
|
struct NN3DWalkersTimeWarpBase : public CommonRigidBodyBase
|
|
{
|
|
NN3DWalkersTimeWarpBase(struct GUIHelperInterface* helper) : CommonRigidBodyBase(helper),
|
|
mPhysicsStepsPerSecondUpdated(false),
|
|
mFramesPerSecondUpdated(false),
|
|
mSolverIterationsUpdated(false)
|
|
{
|
|
// main frame timer initialization
|
|
mApplicationStart = mLoopTimer.getTimeMilliseconds(); /**!< Initialize when the application started running */
|
|
mInputClock = mApplicationStart; /**!< Initialize the last time the input was updated */
|
|
mPreviousModelIteration = mApplicationStart;
|
|
mThisModelIteration = mApplicationStart;
|
|
mApplicationRuntime = mThisModelIteration - mApplicationStart; /**!< Initialize the application runtime */
|
|
|
|
// sub frame time initializations
|
|
mGraphicsStart = mApplicationStart; /** !< Initialize the last graphics start */
|
|
mModelStart = mApplicationStart; /** !< Initialize the last model start */
|
|
mInputStart = mApplicationStart; /** !< Initialize the last input start */
|
|
|
|
mPhysicsStepStart = mApplicationStart; /**!< Initialize the physics step start */
|
|
mPhysicsStepEnd = mApplicationStart; /**!< Initialize the physics step end */
|
|
|
|
//durations
|
|
mLastGraphicsTick = 0;
|
|
mLastModelTick = 0;
|
|
mLastInputTick = 0;
|
|
mPhysicsTick = 0;
|
|
|
|
mInputDt = 0;
|
|
mModelAccumulator = 0;
|
|
mFrameTime = 0;
|
|
|
|
fpsTimeStamp = mLoopTimer.getTimeMilliseconds(); // to time the fps
|
|
fpsStep = 1000.0f / gFramesPerSecond;
|
|
|
|
// performance measurements for this demo
|
|
performanceTimestamp = 0;
|
|
performedTime = 0; // time the physics steps consumed
|
|
speedUpPrintTimeStamp = mLoopTimer.getTimeSeconds(); // timer to print the speed up periodically
|
|
mLoopTimer.reset();
|
|
}
|
|
|
|
~NN3DWalkersTimeWarpBase()
|
|
{
|
|
}
|
|
|
|
void initPhysics()
|
|
{ // initialize the demo
|
|
|
|
setupBasicParamInterface(); // setup adjustable sliders and buttons for parameters
|
|
|
|
m_guiHelper->setUpAxis(1); // Set Y axis as Up axis
|
|
|
|
createEmptyDynamicsWorld(); // create an empty dynamic world
|
|
|
|
m_guiHelper->autogenerateGraphicsObjects(m_dynamicsWorld);
|
|
}
|
|
|
|
void setupBasicParamInterface()
|
|
{ // setup the adjustable sliders and button for parameters
|
|
|
|
{ // create a slider to adjust the simulation speed
|
|
// Force increase the simulation speed to run the simulation with the same accuracy but a higher speed
|
|
SliderParams slider("Simulation speed",
|
|
&gSimulationSpeed);
|
|
slider.m_minVal = gMinSpeed;
|
|
slider.m_maxVal = gMaxSpeed;
|
|
slider.m_callback = clampToCustomSpeedNotches;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
{ // create a button to switch to headless simulation
|
|
// This turns off the graphics update and therefore results in more time for the model update
|
|
ButtonParams button("Run headless", 0, true);
|
|
button.m_callback = switchHeadless;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerButtonParameter(
|
|
button);
|
|
}
|
|
|
|
{ // create a button to switch to maximum speed simulation (fully deterministic)
|
|
// Interesting to test the maximal achievable speed on this hardware
|
|
ButtonParams button("Run maximum speed", 0, true);
|
|
button.m_callback = switchMaximumSpeed;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerButtonParameter(
|
|
button);
|
|
}
|
|
|
|
{ // create a button to switch bullet to perform interpolated substeps to speed up simulation
|
|
// generally, interpolated steps are a good speed-up and should only be avoided if higher accuracy is needed (research purposes etc.)
|
|
ButtonParams button("Perform interpolated substeps", 0, true);
|
|
button.m_callback = switchInterpolated;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerButtonParameter(
|
|
button);
|
|
}
|
|
}
|
|
|
|
void setupAdvancedParamInterface()
|
|
{
|
|
solverTypes[0] = SolverType::SEQUENTIALIMPULSESOLVER;
|
|
solverTypes[1] = SolverType::GAUSSSEIDELSOLVER;
|
|
solverTypes[2] = SolverType::NNCGSOLVER;
|
|
solverTypes[3] = SolverType::DANZIGSOLVER;
|
|
solverTypes[4] = SolverType::LEMKESOLVER;
|
|
|
|
|
|
{
|
|
ComboBoxParams comboParams;
|
|
comboParams.m_comboboxId = 0;
|
|
comboParams.m_numItems = NUM_SOLVERS;
|
|
comboParams.m_startItem = SOLVER_TYPE;
|
|
comboParams.m_callback = changeSolver;
|
|
|
|
comboParams.m_items = solverTypes;
|
|
m_guiHelper->getParameterInterface()->registerComboBox(comboParams);
|
|
}
|
|
|
|
{ // create a slider to adjust the number of internal application ticks
|
|
// The set application tick should contain enough time to perform a full cycle of model update (physics and input)
|
|
// and view update (graphics) with average application load. The graphics and input update determine the remaining time
|
|
// for the physics update
|
|
SliderParams slider("Application Ticks",
|
|
&gApplicationFrequency);
|
|
slider.m_minVal = gMinSpeed;
|
|
slider.m_maxVal = gMaxSpeed;
|
|
slider.m_callback = setApplicationTick;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
{ // create a slider to adjust the number of physics steps per second
|
|
// The default number of steps is at 60, which is appropriate for most general simulations
|
|
// For simulations with higher complexity or if you experience undesired behavior, try increasing the number of steps per second
|
|
// Alternatively, try increasing the number of solver iterations if you experience jittering constraints due to non-converging solutions
|
|
SliderParams slider("Physics steps per second", &gPhysicsStepsPerSecond);
|
|
slider.m_minVal = 0;
|
|
slider.m_maxVal = 1000;
|
|
slider.m_callback = twxChangePhysicsStepsPerSecond;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
{ // create a slider to adjust the number of frames per second
|
|
SliderParams slider("Frames per second", &gFramesPerSecond);
|
|
slider.m_minVal = 0;
|
|
slider.m_maxVal = 200;
|
|
slider.m_callback = twxChangeFPS;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
{ // create a slider to adjust the number of solver iterations to converge to a solution
|
|
// more complex simulations might need a higher number of iterations to converge, it also
|
|
// depends on the type of solver.
|
|
SliderParams slider(
|
|
"Solver interations",
|
|
&gSolverIterations);
|
|
slider.m_minVal = 0;
|
|
slider.m_maxVal = 1000;
|
|
slider.m_callback = twxChangePhysicsStepsPerSecond;
|
|
slider.m_clampToIntegers = true;
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
// ERP/CFM sliders
|
|
// Advanced users: Check descriptions of ERP/CFM in BulletUtils.cpp
|
|
|
|
{ // create a slider to adjust ERP Spring k constant
|
|
SliderParams slider("Global ERP Spring k (F=k*x)", &gERPSpringK);
|
|
slider.m_minVal = 0;
|
|
slider.m_maxVal = 10;
|
|
slider.m_callback = twxChangeERPCFM;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
{ // create a slider to adjust ERP damper c constant
|
|
SliderParams slider("Global ERP damper c (F=c*xdot)", &gERPDamperC);
|
|
slider.m_minVal = 0;
|
|
slider.m_maxVal = 10;
|
|
slider.m_callback = twxChangeERPCFM;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
{ // create a slider to adjust CFM Spring k constant
|
|
SliderParams slider("Global CFM Spring k (F=k*x)", &gCFMSpringK);
|
|
slider.m_minVal = 0;
|
|
slider.m_maxVal = 10;
|
|
slider.m_callback = twxChangeERPCFM;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
{ // create a slider to adjust CFM damper c constant
|
|
SliderParams slider("Global CFM damper c (F=c*xdot)", &gCFMDamperC);
|
|
slider.m_minVal = 0;
|
|
slider.m_maxVal = 10;
|
|
slider.m_callback = twxChangeERPCFM;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
|
|
{ // create a slider to adjust CFM damper c constant
|
|
SliderParams slider("Global CFM singularity avoidance", &gCFMSingularityAvoidance);
|
|
slider.m_minVal = 0;
|
|
slider.m_maxVal = 10;
|
|
slider.m_callback = twxChangeERPCFM;
|
|
slider.m_clampToNotches = false;
|
|
if (m_guiHelper->getParameterInterface())
|
|
m_guiHelper->getParameterInterface()->registerSliderFloatParameter(
|
|
slider);
|
|
}
|
|
}
|
|
|
|
void createEmptyDynamicsWorld()
|
|
{ // create an empty dynamics worlds according to the chosen settings via statics (top section of code)
|
|
|
|
///collision configuration contains default setup for memory, collision setup
|
|
m_collisionConfiguration = new btDefaultCollisionConfiguration();
|
|
//m_collisionConfiguration->setConvexConvexMultipointIterations();
|
|
|
|
///use the default collision dispatcher. For parallel processing you can use a diffent dispatcher (see Extras/BulletMultiThreaded)
|
|
m_dispatcher = new btCollisionDispatcher(m_collisionConfiguration);
|
|
|
|
// default broadphase
|
|
m_broadphase = new btDbvtBroadphase();
|
|
|
|
// different solvers require different settings
|
|
switch (SOLVER_TYPE)
|
|
{
|
|
case SEQUENTIALIMPULSESOLVER:
|
|
{
|
|
// b3Printf("=%s=",SolverType::SEQUENTIALIMPULSESOLVER);
|
|
m_solver = new btSequentialImpulseConstraintSolver();
|
|
break;
|
|
}
|
|
case NNCGSOLVER:
|
|
{
|
|
// b3Printf("=%s=",SolverType::NNCGSOLVER);
|
|
m_solver = new btNNCGConstraintSolver();
|
|
break;
|
|
}
|
|
case DANZIGSOLVER:
|
|
{
|
|
// b3Printf("=%s=",SolverType::DANZIGSOLVER);
|
|
btDantzigSolver* mlcp = new btDantzigSolver();
|
|
m_solver = new btMLCPSolver(mlcp);
|
|
break;
|
|
}
|
|
case GAUSSSEIDELSOLVER:
|
|
{
|
|
// b3Printf("=%s=",SolverType::GAUSSSEIDELSOLVER);
|
|
btSolveProjectedGaussSeidel* mlcp = new btSolveProjectedGaussSeidel();
|
|
m_solver = new btMLCPSolver(mlcp);
|
|
break;
|
|
}
|
|
case LEMKESOLVER:
|
|
{
|
|
// b3Printf("=%s=",SolverType::LEMKESOLVER);
|
|
btLemkeSolver* mlcp = new btLemkeSolver();
|
|
m_solver = new btMLCPSolver(mlcp);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (1)
|
|
{
|
|
//TODO: Set parameters for other solvers
|
|
|
|
m_dynamicsWorld = new btDiscreteDynamicsWorld(m_dispatcher,
|
|
m_broadphase, m_solver, m_collisionConfiguration);
|
|
|
|
if (SOLVER_TYPE == DANZIGSOLVER || SOLVER_TYPE == GAUSSSEIDELSOLVER)
|
|
{
|
|
m_dynamicsWorld->getSolverInfo().m_minimumSolverBatchSize = 1; //for mlcp solver it is better to have a small A matrix
|
|
}
|
|
else
|
|
{
|
|
m_dynamicsWorld->getSolverInfo().m_minimumSolverBatchSize = 128; //for direct solver, it is better to solve multiple objects together, small batches have high overhead
|
|
}
|
|
|
|
m_dynamicsWorld->getDispatchInfo().m_useContinuous = true; // set continuous collision
|
|
}
|
|
else
|
|
{
|
|
//use btMultiBodyDynamicsWorld for Featherstone btMultiBody support
|
|
m_dynamicsWorld = new btMultiBodyDynamicsWorld(m_dispatcher,
|
|
m_broadphase, (btMultiBodyConstraintSolver*)m_solver,
|
|
m_collisionConfiguration);
|
|
}
|
|
|
|
changeERPCFM(); // set appropriate ERP/CFM values according to the string and damper properties of the constraint
|
|
|
|
if (useSplitImpulse)
|
|
{ // If you experience strong repulsion forces in your constraints, it might help to enable the split impulse feature
|
|
m_dynamicsWorld->getSolverInfo().m_splitImpulse = 1; //enable split impulse feature
|
|
// m_dynamicsWorld->getSolverInfo().m_splitImpulsePenetrationThreshold =
|
|
// -0.02;
|
|
// m_dynamicsWorld->getSolverInfo().m_erp2 = BulletUtils::getERP(
|
|
// fixedPhysicsStepSizeSec, 10, 1);
|
|
// m_dynamicsWorld->getSolverInfo().m_splitImpulseTurnErp =
|
|
// BulletUtils::getERP(fixedPhysicsStepSizeSec, 10, 1);
|
|
// b3Printf("Using split impulse feature with ERP/TurnERP: (%f,%f)",
|
|
// m_dynamicsWorld->getSolverInfo().m_erp2,
|
|
// m_dynamicsWorld->getSolverInfo().m_splitImpulseTurnErp);
|
|
}
|
|
|
|
m_dynamicsWorld->getSolverInfo().m_numIterations = gSolverIterations; // set the number of solver iterations for iteration based solvers
|
|
|
|
m_dynamicsWorld->setGravity(btVector3(0, -9.81f, 0)); // set gravity to -9.81
|
|
}
|
|
|
|
btScalar calculatePerformedSpeedup()
|
|
{ // calculate performed speedup
|
|
// we calculate the performed speed up
|
|
btScalar speedUp = ((double)performedTime * 1000.0) / ((double)(mLoopTimer.getTimeMilliseconds() - performanceTimestamp));
|
|
// b3Printf("Avg Effective speedup: %f",speedUp);
|
|
performedTime = 0;
|
|
performanceTimestamp = mLoopTimer.getTimeMilliseconds();
|
|
return speedUp;
|
|
}
|
|
|
|
void timeWarpSimulation(float deltaTime) // Override this
|
|
{
|
|
}
|
|
|
|
void stepSimulation(float deltaTime)
|
|
{ // customly step the simulation
|
|
do
|
|
{
|
|
// // settings
|
|
if (mPhysicsStepsPerSecondUpdated)
|
|
{
|
|
changePhysicsStepsPerSecond(gPhysicsStepsPerSecond);
|
|
mPhysicsStepsPerSecondUpdated = false;
|
|
}
|
|
|
|
if (mFramesPerSecondUpdated)
|
|
{
|
|
changeFPS(gFramesPerSecond);
|
|
mFramesPerSecondUpdated = false;
|
|
}
|
|
|
|
if (gChangeErpCfm)
|
|
{
|
|
changeERPCFM();
|
|
gChangeErpCfm = false;
|
|
}
|
|
|
|
if (mSolverIterationsUpdated)
|
|
{
|
|
changeSolverIterations(gSolverIterations);
|
|
mSolverIterationsUpdated = false;
|
|
}
|
|
|
|
// structure according to the canonical game loop
|
|
// http://www.bulletphysics.org/mediawiki-1.5.8/index.php/Canonical_Game_Loop
|
|
|
|
//##############
|
|
// breaking conditions - if the loop should stop, then check it here
|
|
|
|
//#############
|
|
// model update - here you perform updates of your model, be it the physics model, the game or simulation state or anything not related to graphics and input
|
|
|
|
timeWarpSimulation(deltaTime);
|
|
if (mLoopTimer.getTimeSeconds() - speedUpPrintTimeStamp > 1)
|
|
{
|
|
// on reset, we calculate the performed speed up
|
|
//double speedUp = ((double)performedTime*1000.0)/((double)(mLoopTimer.getTimeMilliseconds()-performanceTimestamp));
|
|
// b3Printf("Avg Effective speedup: %f",speedUp);
|
|
performedTime = 0;
|
|
performanceTimestamp = mLoopTimer.getTimeMilliseconds();
|
|
speedUpPrintTimeStamp = mLoopTimer.getTimeSeconds();
|
|
}
|
|
|
|
// update timers
|
|
mThisModelIteration = mLoopTimer.getTimeMilliseconds();
|
|
mFrameTime = mThisModelIteration - mPreviousModelIteration; /**!< Calculate the frame time (in Milliseconds) */
|
|
mPreviousModelIteration = mThisModelIteration;
|
|
|
|
// b3Printf("Current Frame time: % u", mFrameTime);
|
|
|
|
mApplicationRuntime = mThisModelIteration - mApplicationStart; /**!< Update main frame timer (in Milliseconds) */
|
|
|
|
mModelStart = mLoopTimer.getTimeMilliseconds(); /**!< Begin with the model update (in Milliseconds)*/
|
|
mLastGraphicsTick = mModelStart - mGraphicsStart; /**!< Update graphics timer (in Milliseconds) */
|
|
|
|
if (gMaximumSpeed /** If maximum speed is enabled*/)
|
|
{
|
|
performMaxStep();
|
|
}
|
|
else
|
|
{ /**!< This mode tries to progress as much time as it is expected from the game loop*/
|
|
performSpeedStep();
|
|
}
|
|
|
|
mInputStart = mLoopTimer.getTimeMilliseconds(); /**!< Start the input update */
|
|
mLastModelTick = mInputStart - mModelStart; /**!< Calculate the time the model update took */
|
|
|
|
//#############
|
|
// Input update - Game Clock part of the loop
|
|
/** This runs once every gApplicationTick milliseconds on average */
|
|
mInputDt = mThisModelIteration - mInputClock;
|
|
if (mInputDt >= gApplicationTick)
|
|
{
|
|
mInputClock = mThisModelIteration;
|
|
// mInputHandler.injectInput(); /**!< Inject input into handlers */
|
|
// mInputHandler.update(mInputClock); /**!< update elements that work on the current input state */
|
|
}
|
|
|
|
mGraphicsStart = mLoopTimer.getTimeMilliseconds(); /**!< Start the graphics update */
|
|
mLastInputTick = mGraphicsStart - mInputStart; /**!< Calculate the time the input injection took */
|
|
|
|
//#############
|
|
// Graphics update - Here you perform the representation of your model, meaning graphics rendering according to what your game or simulation model describes
|
|
// In the example browser, there is a separate method called renderScene() for this
|
|
|
|
// Uncomment this for some detailed output about the application ticks
|
|
// b3Printf(
|
|
// "Physics time: %u milliseconds / Graphics time: %u milliseconds / Input time: %u milliseconds / Total time passed: %u milliseconds",
|
|
// mLastModelTick, mLastGraphicsTick, mLastInputTick, mApplicationRuntime);
|
|
|
|
} while (mLoopTimer.getTimeMilliseconds() - fpsTimeStamp < fpsStep); // escape the loop if it is time to render
|
|
// Unfortunately, the input is not included in the loop, therefore the input update frequency is equal to the fps
|
|
|
|
fpsTimeStamp = mLoopTimer.getTimeMilliseconds();
|
|
}
|
|
|
|
virtual bool keyboardCallback(int key, int state)
|
|
{
|
|
switch (key)
|
|
{
|
|
case '1':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::QUARTER_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '2':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::HALF_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '3':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::NORMAL_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '4':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::DOUBLE_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '5':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::QUADRUPLE_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '6':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::DECUPLE_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '7':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::CENTUPLE_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '8':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::QUINCENTUPLE_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '9':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::MILLITUPLE_SPEED;
|
|
gMaximumSpeed = false;
|
|
return true;
|
|
}
|
|
case '0':
|
|
{
|
|
gSimulationSpeed = SimulationSpeeds::MAX_SPEED;
|
|
gMaximumSpeed = true;
|
|
return true;
|
|
}
|
|
}
|
|
return CommonRigidBodyBase::keyboardCallback(key, state);
|
|
}
|
|
|
|
void changePhysicsStepsPerSecond(float physicsStepsPerSecond)
|
|
{ // change the simulation accuracy
|
|
if (m_dynamicsWorld && physicsStepsPerSecond)
|
|
{
|
|
fixedPhysicsStepSizeSec = 1.0f / physicsStepsPerSecond;
|
|
fixedPhysicsStepSizeMilli = 1000.0f / physicsStepsPerSecond;
|
|
|
|
changeERPCFM();
|
|
}
|
|
}
|
|
|
|
void changeERPCFM()
|
|
{ // Change ERP/CFM appropriately to the timestep and the ERP/CFM parameters above
|
|
if (m_dynamicsWorld)
|
|
{
|
|
m_dynamicsWorld->getSolverInfo().m_erp = b3ERPCFMHelper::getERP( // set the error reduction parameter
|
|
fixedPhysicsStepSizeSec, // step size per second
|
|
gERPSpringK, // k of a spring in the equation F = k * x (x:position)
|
|
gERPDamperC); // k of a damper in the equation F = k * v (v:velocity)
|
|
|
|
m_dynamicsWorld->getSolverInfo().m_globalCfm = b3ERPCFMHelper::getCFM( // set the constraint force mixing according to the time step
|
|
gCFMSingularityAvoidance, // singularity avoidance (if you experience unsolvable constraints, increase this value
|
|
fixedPhysicsStepSizeSec, // steps size per second
|
|
gCFMSpringK, // k of a spring in the equation F = k * x (x:position)
|
|
gCFMDamperC); // k of a damper in the equation F = k * v (v:velocity)
|
|
|
|
// b3Printf("Bullet DynamicsWorld ERP: %f",
|
|
// m_dynamicsWorld->getSolverInfo().m_erp);
|
|
|
|
// b3Printf("Bullet DynamicsWorld CFM: %f",
|
|
// m_dynamicsWorld->getSolverInfo().m_globalCfm);
|
|
}
|
|
}
|
|
|
|
void changeSolverIterations(int iterations)
|
|
{ // change the number of iterations
|
|
m_dynamicsWorld->getSolverInfo().m_numIterations = iterations;
|
|
}
|
|
|
|
void changeFPS(float framesPerSecond)
|
|
{ // change the frames per second
|
|
fpsStep = 1000.0f / gFramesPerSecond;
|
|
}
|
|
|
|
void performTrueSteps(btScalar timeStep)
|
|
{ // physics stepping without interpolated substeps
|
|
int subSteps = floor((timeStep / fixedPhysicsStepSizeSec) + 0.5); /**!< Calculate the number of full normal time steps we can take */
|
|
|
|
for (int i = 0; i < subSteps; i++)
|
|
{ /**!< Perform the number of substeps to reach the timestep*/
|
|
if (timeStep && m_dynamicsWorld)
|
|
{
|
|
// since we want to perform all proper steps, we perform no interpolated substeps
|
|
int subSteps = 1;
|
|
|
|
m_dynamicsWorld->stepSimulation(btScalar(timeStep),
|
|
btScalar(subSteps), btScalar(fixedPhysicsStepSizeSec));
|
|
}
|
|
}
|
|
}
|
|
|
|
void performInterpolatedSteps(btScalar timeStep)
|
|
{ // physics stepping with interpolated substeps
|
|
int subSteps = 1 + floor((timeStep / fixedPhysicsStepSizeSec) + 0.5); /**!< Calculate the number of full normal time steps we can take, plus 1 for safety of not losing time */
|
|
if (timeStep && m_dynamicsWorld)
|
|
{
|
|
m_dynamicsWorld->stepSimulation(btScalar(timeStep), btScalar(subSteps),
|
|
btScalar(fixedPhysicsStepSizeSec)); /**!< Perform the number of substeps to reach the timestep*/
|
|
}
|
|
}
|
|
|
|
void performMaxStep()
|
|
{ // perform as many steps as possible
|
|
if (gApplicationTick >= mLastGraphicsTick + mLastInputTick)
|
|
{ // if the remaining time for graphics is going to be positive
|
|
mPhysicsTick = gApplicationTick /**!< calculate the remaining time for physics (in Milliseconds) */
|
|
- mLastGraphicsTick - mLastInputTick;
|
|
}
|
|
else
|
|
{
|
|
mPhysicsTick = 0; // no time for physics left / The internal application step is too high
|
|
}
|
|
|
|
// b3Printf("Application tick: %u",gApplicationTick);
|
|
// b3Printf("Graphics tick: %u",mLastGraphicsTick);
|
|
// b3Printf("Input tick: %u",mLastInputTick);
|
|
// b3Printf("Physics tick: %u",mPhysicsTick);
|
|
|
|
if (mPhysicsTick > 0)
|
|
{ // with positive physics tick we perform as many update steps until the time for it is used up
|
|
|
|
mPhysicsStepStart = mLoopTimer.getTimeMilliseconds(); /**!< The physics updates start (in Milliseconds)*/
|
|
mPhysicsStepEnd = mPhysicsStepStart;
|
|
|
|
while (mPhysicsTick > mPhysicsStepEnd - mPhysicsStepStart)
|
|
{ /**!< Update the physics until we run out of time (in Milliseconds) */
|
|
// b3Printf("Physics passed: %u", mPhysicsStepEnd - mPhysicsStepStart);
|
|
double timeStep = fixedPhysicsStepSizeSec; /**!< update the world (in Seconds) */
|
|
|
|
if (gInterpolate)
|
|
{
|
|
performInterpolatedSteps(timeStep);
|
|
}
|
|
else
|
|
{
|
|
performTrueSteps(timeStep);
|
|
}
|
|
performedTime += timeStep;
|
|
mPhysicsStepEnd = mLoopTimer.getTimeMilliseconds(); /**!< Update the last physics step end to stop updating in time (in Milliseconds) */
|
|
}
|
|
}
|
|
}
|
|
|
|
void performSpeedStep()
|
|
{ // force-perform the number of steps needed to achieve a certain speed (safe to too high speeds, meaning the application will lose time, not the physics)
|
|
if (mFrameTime > gApplicationTick)
|
|
{ /** cap frametime to make the application lose time, not the physics (in Milliseconds) */
|
|
mFrameTime = gApplicationTick; // This prevents the physics time accumulator to sum up too much time
|
|
} // The simulation therefore gets slower, but still performs all requested physics steps
|
|
|
|
mModelAccumulator += mFrameTime; /**!< Accumulate the time the physics simulation has to perform in order to stay in real-time (in Milliseconds) */
|
|
// b3Printf("Model time accumulator: %u", mModelAccumulator);
|
|
|
|
int steps = floor(mModelAccumulator / fixedPhysicsStepSizeMilli); /**!< Calculate the number of time steps we can take */
|
|
// b3Printf("Next steps: %i", steps);
|
|
|
|
if (steps > 0)
|
|
{ /**!< Update if we can take at least one step */
|
|
|
|
double timeStep = gSimulationSpeed * steps * fixedPhysicsStepSizeSec; /**!< update the universe (in Seconds) */
|
|
|
|
if (gInterpolate)
|
|
{
|
|
performInterpolatedSteps(timeStep); // perform interpolated steps
|
|
}
|
|
else
|
|
{
|
|
performTrueSteps(timeStep); // perform full steps
|
|
}
|
|
performedTime += timeStep; // sum up the performed time for measuring the speed up
|
|
mModelAccumulator -= steps * fixedPhysicsStepSizeMilli; /**!< Remove the time performed by the physics simulation from the accumulator, the remaining time carries over to the next cycle (in Milliseconds) */
|
|
}
|
|
}
|
|
|
|
void renderScene()
|
|
{ // render the scene
|
|
if (!gIsHeadless)
|
|
{ // while the simulation is not running headlessly, render to screen
|
|
CommonRigidBodyBase::renderScene();
|
|
|
|
if (m_dynamicsWorld->getDebugDrawer())
|
|
{
|
|
debugDraw(m_dynamicsWorld->getDebugDrawer()->getDebugMode());
|
|
}
|
|
}
|
|
mIsHeadless = gIsHeadless;
|
|
}
|
|
void resetCamera()
|
|
{ // reset the camera to its original position
|
|
float dist = 41;
|
|
float pitch = 52;
|
|
float yaw = 35;
|
|
float targetPos[3] = {0, 0.46, 0};
|
|
m_guiHelper->resetCamera(dist, pitch, yaw, targetPos[0], targetPos[1],
|
|
targetPos[2]);
|
|
}
|
|
|
|
// loop timing components ###################
|
|
//# loop timestamps
|
|
btClock mLoopTimer; /**!< The loop timer to time the loop correctly */
|
|
unsigned long int mApplicationStart; /**!< The time the application was started (absolute, in Milliseconds) */
|
|
unsigned long int mPreviousModelIteration; /**!< The previous model iteration timestamp (absolute, in Milliseconds) */
|
|
unsigned long int mThisModelIteration; /**!< This model iteration timestamp (absolute, in Milliseconds) */
|
|
|
|
//# loop durations
|
|
long int mModelAccumulator; /**!< The time to forward the model in this loop iteration (relative, in Milliseconds) */
|
|
unsigned long int mFrameTime; /**!< The time to render a frame (relative, in Milliseconds) */
|
|
unsigned long int mApplicationRuntime; /**!< The total application runtime (relative, in Milliseconds) */
|
|
|
|
long int mInputDt; /**!< The time difference of input that has to be fed in */
|
|
unsigned long int mInputClock;
|
|
|
|
long int mLastGraphicsTick; /*!< The time it took the graphics rendering last time (relative, in Milliseconds) */
|
|
unsigned long int mGraphicsStart;
|
|
|
|
long int mLastInputTick; /**!< The time it took the input to process last time (relative, in Milliseconds) */
|
|
unsigned long int mInputStart;
|
|
|
|
long int mLastModelTick; /**!< The time it took the model to update last time
|
|
This includes the bullet physics update */
|
|
unsigned long int mModelStart; /**!< The timestamp the model started updating last (absolute, in Milliseconds)*/
|
|
|
|
long int mPhysicsTick; /**!< The time remaining in the loop to update the physics (relative, in Milliseconds)*/
|
|
unsigned long int mPhysicsStepStart; /**!< The physics start timestamp (absolute, in Milliseconds) */
|
|
unsigned long int mPhysicsStepEnd; /**!< The last physics step end (absolute, in Milliseconds) */
|
|
|
|
// to measure the performance of the demo
|
|
double performedTime;
|
|
unsigned long int performanceTimestamp;
|
|
|
|
unsigned long int speedUpPrintTimeStamp;
|
|
|
|
unsigned long int fpsTimeStamp; /**!< FPS timing variables */
|
|
double fpsStep;
|
|
|
|
//store old values
|
|
bool mPhysicsStepsPerSecondUpdated;
|
|
bool mFramesPerSecondUpdated;
|
|
bool mSolverIterationsUpdated;
|
|
bool mIsHeadless;
|
|
};
|
|
|
|
#endif //NN3D_WALKERS_TIME_WARP_BASE_H
|