From da03d8ce5a40fb635a8f7b5fed8f94b0e65075fb Mon Sep 17 00:00:00 2001 From: Lunkhound Date: Mon, 21 Nov 2016 01:43:26 -0800 Subject: [PATCH 1/4] options to change solver type and mode Conflicts: examples/MultiThreadedDemo/CommonRigidBodyMTBase.h --- .../CommonRigidBodyMTBase.cpp | 159 +++++++++++++++++- .../MultiThreadedDemo/CommonRigidBodyMTBase.h | 26 +++ 2 files changed, 177 insertions(+), 8 deletions(-) diff --git a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp index 5a17ba8c2..56872e8a5 100644 --- a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp +++ b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp @@ -30,6 +30,11 @@ class btCollisionShape; #include "BulletDynamics/Dynamics/btSimulationIslandManagerMt.h" // for setSplitIslands() #include "BulletDynamics/Dynamics/btDiscreteDynamicsWorldMt.h" #include "BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolver.h" +#include "BulletDynamics/ConstraintSolver/btNNCGConstraintSolver.h" +#include "BulletDynamics/MLCPSolvers/btMLCPSolver.h" +#include "BulletDynamics/MLCPSolvers/btSolveProjectedGaussSeidel.h" +#include "BulletDynamics/MLCPSolvers/btDantzigSolver.h" +#include "BulletDynamics/MLCPSolvers/btLemkeSolver.h" TaskManager gTaskMgr; @@ -587,11 +592,47 @@ public: }; + +btConstraintSolver* createSolverByType( SolverType t ) +{ + btMLCPSolverInterface* mlcpSolver = NULL; + switch ( t ) + { + case SOLVER_TYPE_SEQUENTIAL_IMPULSE: + return new btSequentialImpulseConstraintSolver(); + case SOLVER_TYPE_NNCG: + return new btNNCGConstraintSolver(); + case SOLVER_TYPE_MLCP_PGS: + mlcpSolver = new btSolveProjectedGaussSeidel(); + break; + case SOLVER_TYPE_MLCP_DANTZIG: + mlcpSolver = new btDantzigSolver(); + break; + case SOLVER_TYPE_MLCP_LEMKE: + mlcpSolver = new btLemkeSolver(); + break; + } + if (mlcpSolver) + { + return new btMLCPSolver(mlcpSolver); + } + return NULL; +} + + static bool gMultithreadedWorld = false; static bool gDisplayProfileInfo = false; -static btScalar gSliderNumThreads = 1.0f; // should be int +static SolverType gSolverType = SOLVER_TYPE_SEQUENTIAL_IMPULSE; +static int gSolverMode = SOLVER_SIMD | + SOLVER_USE_WARMSTARTING | + // SOLVER_RANDMIZE_ORDER | + // SOLVER_INTERLEAVE_CONTACT_AND_FRICTION_CONSTRAINTS | + // SOLVER_USE_2_FRICTION_DIRECTIONS | + 0; static btScalar gSliderSolverIterations = 10.0f; // should be int +static btScalar gSliderNumThreads = 1.0f; // should be int + //////////////////////////////////// CommonRigidBodyMTBase::CommonRigidBodyMTBase( struct GUIHelperInterface* helper ) @@ -622,6 +663,26 @@ void boolPtrButtonCallback(int buttonId, bool buttonState, void* userPointer) } } +void toggleSolverModeCallback(int buttonId, bool buttonState, void* userPointer) +{ + gSolverMode ^= buttonId; + if (CommonRigidBodyMTBase* crb = reinterpret_cast(userPointer)) + { + if (crb->m_dynamicsWorld) + { + crb->m_dynamicsWorld->getSolverInfo().m_solverMode = gSolverMode; + } + } +} + +void setSolverTypeCallback(int buttonId, bool buttonState, void* userPointer) +{ + if (buttonId >= 0 && buttonId < SOLVER_TYPE_COUNT) + { + gSolverType = static_cast(buttonId); + } +} + void apiSelectButtonCallback(int buttonId, bool buttonState, void* userPointer) { gTaskMgr.setApi(static_cast(buttonId)); @@ -658,6 +719,7 @@ void setSolverIterationCountCallback(float val, void* userPtr) void CommonRigidBodyMTBase::createEmptyDynamicsWorld() { gNumIslands = 0; + m_solverType = gSolverType; #if BT_THREADSAFE && (BT_USE_OPENMP || BT_USE_PPL || BT_USE_TBB) m_multithreadCapable = true; #endif @@ -678,9 +740,17 @@ void CommonRigidBodyMTBase::createEmptyDynamicsWorld() m_broadphase = new btDbvtBroadphase(); #if USE_PARALLEL_ISLAND_SOLVER - m_solver = new MyConstraintSolverPool( TaskManager::getMaxNumThreads() ); + { + btConstraintSolver* solvers[ BT_MAX_THREAD_COUNT ]; + int maxThreadCount = min( BT_MAX_THREAD_COUNT, TaskManager::getMaxNumThreads() ); + for ( int i = 0; i < maxThreadCount; ++i ) + { + solvers[ i ] = createSolverByType( m_solverType ); + } + m_solver = new MyConstraintSolverPool( solvers, maxThreadCount ); + } #else - m_solver = new btSequentialImpulseConstraintSolver(); + m_solver = createSolverByType( m_solverType ); #endif //#if USE_PARALLEL_ISLAND_SOLVER btDiscreteDynamicsWorld* world = new MyDiscreteDynamicsWorld( m_dispatcher, m_broadphase, m_solver, m_collisionConfiguration ); m_dynamicsWorld = world; @@ -707,15 +777,14 @@ void CommonRigidBodyMTBase::createEmptyDynamicsWorld() m_broadphase = new btDbvtBroadphase(); - ///the default constraint solver. For parallel processing you can use a different solver (see Extras/BulletMultiThreaded) - btSequentialImpulseConstraintSolver* sol = new btSequentialImpulseConstraintSolver; - m_solver = sol; + m_solver = createSolverByType( m_solverType ); m_dynamicsWorld = new btDiscreteDynamicsWorld( m_dispatcher, m_broadphase, m_solver, m_collisionConfiguration ); } m_dynamicsWorld->setInternalTickCallback( profileBeginCallback, NULL, true ); m_dynamicsWorld->setInternalTickCallback( profileEndCallback, NULL, false ); m_dynamicsWorld->setGravity( btVector3( 0, -10, 0 ) ); + m_dynamicsWorld->getSolverInfo().m_solverMode = gSolverMode; createDefaultParameters(); } @@ -732,12 +801,25 @@ void CommonRigidBodyMTBase::createDefaultParameters() } { // create a button to toggle profile printing - ButtonParams button( "Display profile timings", 0, true ); + ButtonParams button( "Display solver info", 0, true ); button.m_userPointer = &gDisplayProfileInfo; button.m_callback = boolPtrButtonCallback; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); } + + // add buttons for switching to different solver types + for (int i = 0; i < SOLVER_TYPE_COUNT; ++i) { + char buttonName[256]; + SolverType solverType = static_cast(i); + sprintf(buttonName, "Solver Type %s", getSolverTypeName(solverType)); + ButtonParams button( buttonName, 0, true ); + button.m_buttonId = solverType; + button.m_callback = setSolverTypeCallback; + m_guiHelper->getParameterInterface()->registerButtonParameter( button ); + } + { + // a slider for the number of solver iterations SliderParams slider( "Solver iterations", &gSliderSolverIterations ); slider.m_minVal = 1.0f; slider.m_maxVal = 30.0f; @@ -746,6 +828,48 @@ void CommonRigidBodyMTBase::createDefaultParameters() slider.m_clampToIntegers = true; m_guiHelper->getParameterInterface()->registerSliderFloatParameter( slider ); } + { + ButtonParams button( "Solver use SIMD", 0, true ); + button.m_buttonId = SOLVER_SIMD; + button.m_callback = toggleSolverModeCallback; + button.m_userPointer = this; + m_guiHelper->getParameterInterface()->registerButtonParameter( button ); + } + { + ButtonParams button( "Solver randomize order", 0, true ); + button.m_buttonId = SOLVER_RANDMIZE_ORDER; + button.m_callback = toggleSolverModeCallback; + button.m_userPointer = this; + m_guiHelper->getParameterInterface()->registerButtonParameter( button ); + } + { + ButtonParams button( "Solver interleave contact/friction", 0, true ); + button.m_buttonId = SOLVER_INTERLEAVE_CONTACT_AND_FRICTION_CONSTRAINTS; + button.m_callback = toggleSolverModeCallback; + button.m_userPointer = this; + m_guiHelper->getParameterInterface()->registerButtonParameter( button ); + } + { + ButtonParams button( "Solver 2 friction directions", 0, true ); + button.m_buttonId = SOLVER_USE_2_FRICTION_DIRECTIONS; + button.m_callback = toggleSolverModeCallback; + button.m_userPointer = this; + m_guiHelper->getParameterInterface()->registerButtonParameter( button ); + } + { + ButtonParams button( "Solver friction dir caching", 0, true ); + button.m_buttonId = SOLVER_ENABLE_FRICTION_DIRECTION_CACHING; + button.m_callback = toggleSolverModeCallback; + button.m_userPointer = this; + m_guiHelper->getParameterInterface()->registerButtonParameter( button ); + } + { + ButtonParams button( "Solver warmstarting", 0, true ); + button.m_buttonId = SOLVER_USE_WARMSTARTING; + button.m_callback = toggleSolverModeCallback; + button.m_userPointer = this; + m_guiHelper->getParameterInterface()->registerButtonParameter( button ); + } if (m_multithreadedWorld) { // create a button for each supported threading API @@ -781,6 +905,12 @@ void CommonRigidBodyMTBase::drawScreenText() int xCoord = 400; int yCoord = 30; int yStep = 30; + if (m_solverType != gSolverType) + { + sprintf( msg, "restart example to change solver type" ); + m_guiHelper->getAppInterface()->drawText( msg, 300, yCoord, 0.4f ); + yCoord += yStep; + } if (m_multithreadCapable) { if ( m_multithreadedWorld != gMultithreadedWorld ) @@ -815,7 +945,20 @@ void CommonRigidBodyMTBase::drawScreenText() m_guiHelper->getAppInterface()->drawText( msg, 100, yCoord, 0.4f ); yCoord += yStep; } - + { + int sm = gSolverMode; + sprintf( msg, "solver %s mode [%s%s%s%s%s%s]", + getSolverTypeName(m_solverType), + sm & SOLVER_SIMD ? "SIMD" : "", + sm & SOLVER_RANDMIZE_ORDER ? " randomize" : "", + sm & SOLVER_INTERLEAVE_CONTACT_AND_FRICTION_CONSTRAINTS ? " interleave" : "", + sm & SOLVER_USE_2_FRICTION_DIRECTIONS ? " friction2x" : "", + sm & SOLVER_ENABLE_FRICTION_DIRECTION_CACHING ? " frictionDirCaching" : "", + sm & SOLVER_USE_WARMSTARTING ? " warm" : "" + ); + m_guiHelper->getAppInterface()->drawText( msg, xCoord, yCoord, 0.4f ); + yCoord += yStep; + } sprintf( msg, "internalSimStep %5.3f ms", gProfiler.getAverageTime( Profiler::kRecordInternalTimeStep )*0.001f ); diff --git a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.h b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.h index 6ead77d19..a399c032e 100644 --- a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.h +++ b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.h @@ -11,6 +11,31 @@ #include "../CommonInterfaces/CommonGraphicsAppInterface.h" #include "../CommonInterfaces/CommonWindowInterface.h" +enum SolverType +{ + SOLVER_TYPE_SEQUENTIAL_IMPULSE, + SOLVER_TYPE_NNCG, + SOLVER_TYPE_MLCP_PGS, + SOLVER_TYPE_MLCP_DANTZIG, + SOLVER_TYPE_MLCP_LEMKE, + + SOLVER_TYPE_COUNT +}; + +inline const char* getSolverTypeName( SolverType t ) +{ + switch (t) + { + case SOLVER_TYPE_SEQUENTIAL_IMPULSE: return "SequentialImpulse"; + case SOLVER_TYPE_NNCG: return "NNCG"; + case SOLVER_TYPE_MLCP_PGS: return "MLCP ProjectedGaussSeidel"; + case SOLVER_TYPE_MLCP_DANTZIG: return "MLCP Dantzig"; + case SOLVER_TYPE_MLCP_LEMKE: return "MLCP Lemke"; + } + btAssert( !"unhandled solver type in switch" ); + return "???"; +} + struct CommonRigidBodyMTBase : public CommonExampleInterface { //keep the collision shapes, for deletion/cleanup @@ -20,6 +45,7 @@ struct CommonRigidBodyMTBase : public CommonExampleInterface btConstraintSolver* m_solver; btDefaultCollisionConfiguration* m_collisionConfiguration; btDiscreteDynamicsWorld* m_dynamicsWorld; + SolverType m_solverType; bool m_multithreadedWorld; bool m_multithreadCapable; From ea0df70c77b84d730536f3266420247b5a38a031 Mon Sep 17 00:00:00 2001 From: Lunkhound Date: Sun, 27 Nov 2016 02:33:16 -0800 Subject: [PATCH 2/4] example browser: fix for GUI button toggle state --- .../CommonParameterInterface.h | 2 ++ .../GwenGUISupport/GwenParameterInterface.cpp | 16 ++++++++++++---- .../CommonRigidBodyMTBase.cpp | 19 +++++++++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/examples/CommonInterfaces/CommonParameterInterface.h b/examples/CommonInterfaces/CommonParameterInterface.h index e2334108c..da5b35342 100644 --- a/examples/CommonInterfaces/CommonParameterInterface.h +++ b/examples/CommonInterfaces/CommonParameterInterface.h @@ -43,6 +43,7 @@ struct ButtonParams int m_buttonId; void* m_userPointer; bool m_isTrigger; + bool m_initialState; ButtonParamChangedCallback m_callback; ButtonParams(const char* name, int buttonId, bool isTrigger) @@ -50,6 +51,7 @@ struct ButtonParams m_buttonId(buttonId), m_userPointer(0), m_isTrigger(isTrigger), + m_initialState(false), m_callback(0) { } diff --git a/examples/ExampleBrowser/GwenGUISupport/GwenParameterInterface.cpp b/examples/ExampleBrowser/GwenGUISupport/GwenParameterInterface.cpp index aa8c0b871..432137bcd 100644 --- a/examples/ExampleBrowser/GwenGUISupport/GwenParameterInterface.cpp +++ b/examples/ExampleBrowser/GwenGUISupport/GwenParameterInterface.cpp @@ -3,12 +3,14 @@ struct MyButtonEventHandler : public Gwen::Event::Handler { + Gwen::Controls::Button* m_buttonControl; ButtonParamChangedCallback m_callback; void* m_userPointer; int m_buttonId; - MyButtonEventHandler(ButtonParamChangedCallback callback, int buttonId, void* userPointer) - :m_callback(callback), + MyButtonEventHandler(Gwen::Controls::Button* buttonControl, ButtonParamChangedCallback callback, int buttonId, void* userPointer) + :m_buttonControl(buttonControl), + m_callback(callback), m_userPointer(userPointer), m_buttonId(buttonId) { @@ -18,7 +20,12 @@ struct MyButtonEventHandler : public Gwen::Event::Handler { if (m_callback) { - (*m_callback)(m_buttonId, true, m_userPointer); + bool buttonState = true; + if (m_buttonControl->IsToggle()) + { + buttonState = m_buttonControl->GetToggleState(); + } + ( *m_callback )( m_buttonId, buttonState, m_userPointer ); } } }; @@ -140,10 +147,11 @@ void GwenParameterInterface::registerButtonParameter(ButtonParams& params) { Gwen::Controls::Button* button = new Gwen::Controls::Button(m_gwenInternalData->m_demoPage->GetPage()); - MyButtonEventHandler* handler = new MyButtonEventHandler(params.m_callback,params.m_buttonId,params.m_userPointer); + MyButtonEventHandler* handler = new MyButtonEventHandler(button, params.m_callback,params.m_buttonId,params.m_userPointer); button->SetText(params.m_name); button->onPress.Add( handler, &MyButtonEventHandler::onButtonPress ); button->SetIsToggle(params.m_isTrigger); + button->SetToggleState(params.m_initialState); m_paramInternalData->m_buttons.push_back(button); m_paramInternalData->m_buttonEventHandlers.push_back(handler); diff --git a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp index 56872e8a5..d29e3dd71 100644 --- a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp +++ b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp @@ -665,7 +665,14 @@ void boolPtrButtonCallback(int buttonId, bool buttonState, void* userPointer) void toggleSolverModeCallback(int buttonId, bool buttonState, void* userPointer) { - gSolverMode ^= buttonId; + if (buttonState) + { + gSolverMode |= buttonId; + } + else + { + gSolverMode &= ~buttonId; + } if (CommonRigidBodyMTBase* crb = reinterpret_cast(userPointer)) { if (crb->m_dynamicsWorld) @@ -795,6 +802,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() { // create a button to toggle multithreaded world ButtonParams button( "Multithreaded world enable", 0, true ); + button.m_initialState = gMultithreadedWorld; button.m_userPointer = &gMultithreadedWorld; button.m_callback = boolPtrButtonCallback; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); @@ -802,6 +810,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() { // create a button to toggle profile printing ButtonParams button( "Display solver info", 0, true ); + button.m_initialState = gDisplayProfileInfo; button.m_userPointer = &gDisplayProfileInfo; button.m_callback = boolPtrButtonCallback; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); @@ -813,7 +822,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() char buttonName[256]; SolverType solverType = static_cast(i); sprintf(buttonName, "Solver Type %s", getSolverTypeName(solverType)); - ButtonParams button( buttonName, 0, true ); + ButtonParams button( buttonName, 0, false ); button.m_buttonId = solverType; button.m_callback = setSolverTypeCallback; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); @@ -831,6 +840,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() { ButtonParams button( "Solver use SIMD", 0, true ); button.m_buttonId = SOLVER_SIMD; + button.m_initialState = !! (gSolverMode & button.m_buttonId); button.m_callback = toggleSolverModeCallback; button.m_userPointer = this; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); @@ -838,6 +848,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() { ButtonParams button( "Solver randomize order", 0, true ); button.m_buttonId = SOLVER_RANDMIZE_ORDER; + button.m_initialState = !! (gSolverMode & button.m_buttonId); button.m_callback = toggleSolverModeCallback; button.m_userPointer = this; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); @@ -845,6 +856,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() { ButtonParams button( "Solver interleave contact/friction", 0, true ); button.m_buttonId = SOLVER_INTERLEAVE_CONTACT_AND_FRICTION_CONSTRAINTS; + button.m_initialState = !! (gSolverMode & button.m_buttonId); button.m_callback = toggleSolverModeCallback; button.m_userPointer = this; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); @@ -852,6 +864,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() { ButtonParams button( "Solver 2 friction directions", 0, true ); button.m_buttonId = SOLVER_USE_2_FRICTION_DIRECTIONS; + button.m_initialState = !! (gSolverMode & button.m_buttonId); button.m_callback = toggleSolverModeCallback; button.m_userPointer = this; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); @@ -859,6 +872,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() { ButtonParams button( "Solver friction dir caching", 0, true ); button.m_buttonId = SOLVER_ENABLE_FRICTION_DIRECTION_CACHING; + button.m_initialState = !! (gSolverMode & button.m_buttonId); button.m_callback = toggleSolverModeCallback; button.m_userPointer = this; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); @@ -866,6 +880,7 @@ void CommonRigidBodyMTBase::createDefaultParameters() { ButtonParams button( "Solver warmstarting", 0, true ); button.m_buttonId = SOLVER_USE_WARMSTARTING; + button.m_initialState = !! (gSolverMode & button.m_buttonId); button.m_callback = toggleSolverModeCallback; button.m_userPointer = this; m_guiHelper->getParameterInterface()->registerButtonParameter( button ); From b979064817fcba744b4b4cefd5216998e82d8c42 Mon Sep 17 00:00:00 2001 From: Lunkhound Date: Fri, 6 Jan 2017 21:20:44 -0800 Subject: [PATCH 3/4] fix compile error on GCC --- examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp index d29e3dd71..a3a6e742a 100644 --- a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp +++ b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp @@ -749,7 +749,7 @@ void CommonRigidBodyMTBase::createEmptyDynamicsWorld() #if USE_PARALLEL_ISLAND_SOLVER { btConstraintSolver* solvers[ BT_MAX_THREAD_COUNT ]; - int maxThreadCount = min( BT_MAX_THREAD_COUNT, TaskManager::getMaxNumThreads() ); + int maxThreadCount = btMin( int(BT_MAX_THREAD_COUNT), TaskManager::getMaxNumThreads() ); for ( int i = 0; i < maxThreadCount; ++i ) { solvers[ i ] = createSolverByType( m_solverType ); From f65644f3e046508f954c9b024f42495a3919cc5f Mon Sep 17 00:00:00 2001 From: Lunkhound Date: Sun, 29 Jan 2017 21:13:24 -0800 Subject: [PATCH 4/4] fix compile error when BT_THREADSAFE not defined --- examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp index a3a6e742a..6a7634618 100644 --- a/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp +++ b/examples/MultiThreadedDemo/CommonRigidBodyMTBase.cpp @@ -51,6 +51,8 @@ TaskManager gTaskMgr; #define BT_OVERRIDE #endif +static int gNumIslands = 0; + class Profiler { @@ -439,8 +441,6 @@ struct UpdateIslandDispatcher } }; -static int gNumIslands = 0; - void parallelIslandDispatch( btAlignedObjectArray* islandsPtr, btSimulationIslandManagerMt::IslandCallback* callback ) { ProfileHelper prof(Profiler::kRecordDispatchIslands); @@ -746,7 +746,7 @@ void CommonRigidBodyMTBase::createEmptyDynamicsWorld() m_broadphase = new btDbvtBroadphase(); -#if USE_PARALLEL_ISLAND_SOLVER +#if BT_THREADSAFE && USE_PARALLEL_ISLAND_SOLVER { btConstraintSolver* solvers[ BT_MAX_THREAD_COUNT ]; int maxThreadCount = btMin( int(BT_MAX_THREAD_COUNT), TaskManager::getMaxNumThreads() );