diff --git a/examples/ExampleBrowser/CMakeLists.txt b/examples/ExampleBrowser/CMakeLists.txt index 2129051c3..0af74fdb8 100644 --- a/examples/ExampleBrowser/CMakeLists.txt +++ b/examples/ExampleBrowser/CMakeLists.txt @@ -114,7 +114,10 @@ SET(ExtendedTutorialsSources ../ExtendedTutorials/SimpleCloth.cpp ../ExtendedTutorials/Chain.cpp ../ExtendedTutorials/Bridge.cpp - ../ExtendedTutorials/RigidBodyFromObj.cpp + ../ExtendedTutorials/RigidBodyFromObj.cpp + ../ExtendedTutorials/InclinedPlane.cpp + ../ExtendedTutorials/InclinedPlane.h + ../ExtendedTutorials/NewtonsCradle.cpp ) SET(BulletExampleBrowser_SRCS @@ -166,8 +169,8 @@ SET(BulletExampleBrowser_SRCS ../ExtendedTutorials/SimpleCloth.h ../ExtendedTutorials/SimpleJoint.cpp ../ExtendedTutorials/SimpleJoint.h - ../ExtendedTutorials/NewtonianPendulum.cpp - ../ExtendedTutorials/NewtonianPendulum.h + ../ExtendedTutorials/NewtonsCradle.cpp + ../ExtendedTutorials/NewtonsCradle.h ../ExtendedTutorials/InclinedPlane.cpp ../ExtendedTutorials/InclinedPlane.h ../ExtendedTutorials/MultiPendulum.cpp diff --git a/examples/ExampleBrowser/ExampleEntries.cpp b/examples/ExampleBrowser/ExampleEntries.cpp index 3f892b01b..10cfe33ee 100644 --- a/examples/ExampleBrowser/ExampleEntries.cpp +++ b/examples/ExampleBrowser/ExampleEntries.cpp @@ -56,7 +56,7 @@ #endif #endif //B3_USE_CLEW -//Extended Tutorial Includes Added by Mobeen +//Extended Tutorial Includes Added by Mobeen and Benelot #include "../ExtendedTutorials/SimpleBox.h" #include "../ExtendedTutorials/MultipleBoxes.h" #include "../ExtendedTutorials/SimpleJoint.h" @@ -64,6 +64,9 @@ #include "../ExtendedTutorials/Chain.h" #include "../ExtendedTutorials/Bridge.h" #include "../ExtendedTutorials/RigidBodyFromObj.h" +#include "../ExtendedTutorials/InclinedPlane.h" +#include "../ExtendedTutorials/NewtonsCradle.h" +#include "../ExtendedTutorials/MultiPendulum.h" struct ExampleEntry { @@ -269,11 +272,15 @@ static ExampleEntry gDefaultExamples[]= //Extended Tutorials Added by Mobeen ExampleEntry(0,"Extended Tutorials"), ExampleEntry(1,"Simple Box", "Simplest possible demo creating a single box rigid body that falls under gravity", ET_SimpleBoxCreateFunc), - ExampleEntry(1,"Multiple Boxes", "Adding multiple box rigid bodies that fall under gravity", ET_MultipleBoxesCreateFunc), - ExampleEntry(1,"Simple Joint", "Creating a single distance constraint between two box rigid bodies", ET_SimpleJointCreateFunc), - ExampleEntry(1,"Simple Cloth", "Creating a simple piece of cloth", ET_SimpleClothCreateFunc), - ExampleEntry(1,"Simple Chain", "Creating a simple chain using a pair of point2point/distance constraints. You may click and drag any box to see the chain respond.", ET_ChainCreateFunc), - ExampleEntry(1,"Simple Bridge", "Creating a simple bridge using a pair of point2point/distance constraints. You may click and drag any plank to see the bridge respond.", ET_BridgeCreateFunc), + ExampleEntry(1,"Multiple Boxes", "Add multiple box rigid bodies that fall under gravity", ET_MultipleBoxesCreateFunc), + ExampleEntry(1,"Simple Joint", "Create a single distance constraint between two box rigid bodies", ET_SimpleJointCreateFunc), + ExampleEntry(1,"Simple Cloth", "Create a simple piece of cloth", ET_SimpleClothCreateFunc), + ExampleEntry(1,"Simple Chain", "Create a simple chain using a pair of point2point/distance constraints. You may click and drag any box to see the chain respond.", ET_ChainCreateFunc), + ExampleEntry(1,"Simple Bridge", "Create a simple bridge using a pair of point2point/distance constraints. You may click and drag any plank to see the bridge respond.", ET_BridgeCreateFunc), + ExampleEntry(1,"Inclined Plane", "Create an inclined plane to show restitution and different types of friction. Use the sliders to vary restitution and friction and press space to reset the scene.", ET_InclinedPlaneCreateFunc), + ExampleEntry(1,"Newton's Cradle", "Create a Newton's Cradle using a pair of point2point/slider constraints. Press 1/2 to lengthen/shorten the pendula, press 3 to displace pendula. Use the sliders to select the number of pendula in total (reset simulation), the number of displaced pendula and other options.", ET_NewtonsCradleCreateFunc), + ExampleEntry(1,"Multi-Pendulum", "Create a Multi-Pendulum using point2point/slider constraints. Press 1/2 to lengthen/shorten the pendula, press 3 to displace pendula. Use the sliders to select the number of pendula in total (reset simulation), the number of displaced pendula and other options.",ET_MultiPendulumCreateFunc), + //todo: create a category/tutorial about advanced topics, such as optimizations, using different collision detection algorithm, different constraint solvers etc. //ExampleEntry(0,"Advanced"), diff --git a/examples/ExtendedTutorials/InclinedPlane.cpp b/examples/ExtendedTutorials/InclinedPlane.cpp new file mode 100644 index 000000000..dfe099094 --- /dev/null +++ b/examples/ExtendedTutorials/InclinedPlane.cpp @@ -0,0 +1,372 @@ +/* +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. +*/ + + + +#include "InclinedPlane.h" + +#include "btBulletDynamicsCommon.h" +#include "LinearMath/btVector3.h" +#include "LinearMath/btAlignedObjectArray.h" +#include "../CommonInterfaces/CommonRigidBodyBase.h" +#include "../CommonInterfaces/CommonParameterInterface.h" + +static btScalar gTilt = 20.0f/180.0f*SIMD_PI; // tilt the ramp 20 degrees + +static btScalar gRampFriction = 1; // set ramp friction to 1 + +static btScalar gRampRestitution = 0; // set ramp restitution to 0 (no restitution) + +static btScalar gBoxFriction = 1; // set box friction to 1 + +static btScalar gBoxRestitution = 0; // set box restitution to 0 + +static btScalar gSphereFriction = 1; // set sphere friction to 1 + +static btScalar gSphereRollingFriction = 1; // set sphere rolling friction to 1 + +static btScalar gSphereRestitution = 0; // set sphere restitution to 0 + +// handles for changes +static btRigidBody* ramp = NULL; +static btRigidBody* gBox = NULL; +static btRigidBody* gSphere = NULL; + +struct InclinedPlaneExample : public CommonRigidBodyBase +{ + InclinedPlaneExample(struct GUIHelperInterface* helper) + :CommonRigidBodyBase(helper) + { + } + virtual ~InclinedPlaneExample(){} + virtual void initPhysics(); + virtual void resetScene(); + virtual void renderScene(); + virtual void stepSimulation(float deltaTime); + virtual bool keyboardCallback(int key, int state); + void resetCamera() + { + 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]); + } + + + +}; + +void onBoxFrictionChanged(float friction); + +void onBoxRestitutionChanged(float restitution); + +void onSphereFrictionChanged(float friction); + +void onSphereRestitutionChanged(float restitution); + +void onRampInclinationChanged(float inclination); + +void onRampFrictionChanged(float friction); + +void onRampRestitutionChanged(float restitution); + +void InclinedPlaneExample::initPhysics() +{ + + { // create slider to change the ramp tilt + SliderParams slider("Ramp Tilt",&gTilt); + slider.m_minVal=0; + slider.m_maxVal=M_PI/2.0f; + slider.m_clampToNotches = false; + slider.m_callback = onRampInclinationChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter(slider); + } + + { // create slider to change the ramp friction + SliderParams slider("Ramp Friction",&gRampFriction); + slider.m_minVal=0; + slider.m_maxVal=10; + slider.m_clampToNotches = false; + slider.m_callback = onRampFrictionChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter(slider); + } + + { // create slider to change the ramp restitution + SliderParams slider("Ramp Restitution",&gRampRestitution); + slider.m_minVal=0; + slider.m_maxVal=1; + slider.m_clampToNotches = false; + slider.m_callback = onRampRestitutionChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter(slider); + } + + { // create slider to change the box friction + SliderParams slider("Box Friction",&gBoxFriction); + slider.m_minVal=0; + slider.m_maxVal=10; + slider.m_clampToNotches = false; + slider.m_callback = onBoxFrictionChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter(slider); + } + + { // create slider to change the box restitution + SliderParams slider("Box Restitution",&gBoxRestitution); + slider.m_minVal=0; + slider.m_maxVal=1; + slider.m_clampToNotches = false; + slider.m_callback = onBoxRestitutionChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter(slider); + } + + { // create slider to change the sphere friction + SliderParams slider("Sphere Friction",&gSphereFriction); + slider.m_minVal=0; + slider.m_maxVal=10; + slider.m_clampToNotches = false; + slider.m_callback = onSphereFrictionChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter(slider); + } + + { // create slider to change the sphere rolling friction + SliderParams slider("Sphere Rolling Friction",&gSphereRollingFriction); + slider.m_minVal=0; + slider.m_maxVal=10; + slider.m_clampToNotches = false; + slider.m_callback = onSphereRestitutionChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter(slider); + } + + { // create slider to change the sphere restitution + SliderParams slider("Sphere Restitution",&gSphereRestitution); + slider.m_minVal=0; + slider.m_maxVal=1; + slider.m_clampToNotches = false; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter(slider); + } + + m_guiHelper->setUpAxis(1); // set Y axis as up axis + + createEmptyDynamicsWorld(); + + // create debug drawer + m_guiHelper->createPhysicsDebugDrawer(m_dynamicsWorld); + if (m_dynamicsWorld->getDebugDrawer()) + m_dynamicsWorld->getDebugDrawer()->setDebugMode(btIDebugDraw::DBG_DrawWireframe+btIDebugDraw::DBG_DrawContactPoints); + + + { // create a static ground + btBoxShape* groundShape = createBoxShape(btVector3(btScalar(50.),btScalar(50.),btScalar(50.))); + m_collisionShapes.push_back(groundShape); + + btTransform groundTransform; + groundTransform.setIdentity(); + groundTransform.setOrigin(btVector3(0,-50,0)); + + btScalar mass(0.); + createRigidBody(mass,groundTransform,groundShape, btVector4(0,0,1,1)); + } + + { //create a static inclined plane + btBoxShape* inclinedPlaneShape = createBoxShape(btVector3(btScalar(20.),btScalar(1.),btScalar(10.))); + m_collisionShapes.push_back(inclinedPlaneShape); + + btTransform startTransform; + startTransform.setIdentity(); + + // position the inclined plane above ground + startTransform.setOrigin(btVector3( + btScalar(0), + btScalar(15), + btScalar(0))); + + btQuaternion incline; + incline.setRotation(btVector3(0,0,1),gTilt); + startTransform.setRotation(incline); + + btScalar mass(0.); + ramp = createRigidBody(mass,startTransform,inclinedPlaneShape); + ramp->setFriction(gRampFriction); + ramp->setRestitution(gRampRestitution); + } + + + { //create a cube above the inclined plane + btBoxShape* boxShape = createBoxShape(btVector3(1,1,1)); + + m_collisionShapes.push_back(boxShape); + + btTransform startTransform; + startTransform.setIdentity(); + + btScalar boxMass(1.f); + + startTransform.setOrigin( + btVector3(btScalar(0), btScalar(20), btScalar(2))); + + gBox = createRigidBody(boxMass, startTransform, boxShape); + gBox->forceActivationState(DISABLE_DEACTIVATION); // to prevent the box on the ramp from disabling + gBox->setFriction(gBoxFriction); + gBox->setRestitution(gBoxRestitution); + } + + { //create a sphere above the inclined plane + btSphereShape* sphereShape = new btSphereShape(btScalar(1)); + + m_collisionShapes.push_back(sphereShape); + + btTransform startTransform; + startTransform.setIdentity(); + + btScalar sphereMass(1.f); + + startTransform.setOrigin( + btVector3(btScalar(0), btScalar(20), btScalar(4))); + + gSphere = createRigidBody(sphereMass, startTransform, sphereShape); + gSphere->forceActivationState(DISABLE_DEACTIVATION); // to prevent the sphere on the ramp from disabling + gSphere->setFriction(gSphereFriction); + gSphere->setRestitution(gSphereRestitution); + gSphere->setRollingFriction(gSphereRollingFriction); + } + + m_guiHelper->autogenerateGraphicsObjects(m_dynamicsWorld); +} + +void InclinedPlaneExample::resetScene() { + { //reset a cube above the inclined plane + + btTransform startTransform; + startTransform.setIdentity(); + + startTransform.setOrigin( + btVector3(btScalar(0), btScalar(20), btScalar(2))); + + gBox->setWorldTransform(startTransform); + btVector3 zero(0, 0, 0); + gBox->setAngularVelocity(zero); + gBox->setLinearVelocity(zero); + gBox->clearForces(); + } + + { //reset a sphere above the inclined plane + btTransform startTransform; + startTransform.setIdentity(); + + startTransform.setOrigin( + btVector3(btScalar(0), btScalar(20), btScalar(4))); + + gSphere->setWorldTransform(startTransform); + btVector3 zero(0, 0, 0); + gSphere->setAngularVelocity(zero); + gSphere->setLinearVelocity(zero); + gSphere->clearForces(); + } +} + +void InclinedPlaneExample::stepSimulation(float deltaTime) +{ + if (m_dynamicsWorld) + { + m_dynamicsWorld->stepSimulation(deltaTime); + } + +} + + +void InclinedPlaneExample::renderScene() +{ + CommonRigidBodyBase::renderScene(); +} + +bool InclinedPlaneExample::keyboardCallback(int key, int state) { +// b3Printf("Key pressed: %d in state %d \n",key,state); + + switch (key) { + case 32 /*ASCII for space*/: { + resetScene(); + break; + } + } + + return false; +} + + +// GUI parameter modifiers +void onBoxFrictionChanged(float friction){ + if(gBox){ + gBox->setFriction(friction); +// b3Printf("Friction of box changed to %f",friction ); + } +} + +void onBoxRestitutionChanged(float restitution){ + if(gBox){ + gBox->setRestitution(restitution); + //b3Printf("Restitution of box changed to %f",restitution); + } +} + +void onSphereFrictionChanged(float friction){ + if(gSphere){ + gSphere->setFriction(friction); + //b3Printf("Friction of sphere changed to %f",friction ); + } +} + +void onSphereRestitutionChanged(float restitution){ + if(gSphere){ + gSphere->setRestitution(restitution); + //b3Printf("Restitution of sphere changed to %f",restitution); + } +} + +void onRampInclinationChanged(float inclination){ + if(ramp){ + btTransform startTransform; + startTransform.setIdentity(); + + // position the inclined plane above ground + startTransform.setOrigin( + btVector3(btScalar(0), btScalar(15), btScalar(0))); + + btQuaternion incline; + incline.setRotation(btVector3(0,0,1),gTilt); + startTransform.setRotation(incline); + ramp->setWorldTransform(startTransform); + //b3Printf("Inclination of ramp changed to %f",inclination ); + } +} + +void onRampFrictionChanged(float friction){ + if(ramp){ + ramp->setFriction(friction); + //b3Printf("Friction of ramp changed to %f \n",friction ); + } +} + +void onRampRestitutionChanged(float restitution){ + if(ramp){ + ramp->setRestitution(restitution); + //b3Printf("Restitution of ramp changed to %f \n",restitution); + } +} + + +CommonExampleInterface* ET_InclinedPlaneCreateFunc(CommonExampleOptions& options) +{ + return new InclinedPlaneExample(options.m_guiHelper); +} diff --git a/examples/ExtendedTutorials/InclinedPlane.h b/examples/ExtendedTutorials/InclinedPlane.h new file mode 100644 index 000000000..23ea92dfd --- /dev/null +++ b/examples/ExtendedTutorials/InclinedPlane.h @@ -0,0 +1,22 @@ +/* +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 ET_INCLINED_PLANE_EXAMPLE_H +#define ET_INCLINED_PLANE_EXAMPLE_H + +class CommonExampleInterface* ET_InclinedPlaneCreateFunc(struct CommonExampleOptions& options); + + +#endif //ET_INCLINED_PLANE_EXAMPLE_H diff --git a/examples/ExtendedTutorials/MultiPendulum.cpp b/examples/ExtendedTutorials/MultiPendulum.cpp new file mode 100644 index 000000000..8c4cff2e2 --- /dev/null +++ b/examples/ExtendedTutorials/MultiPendulum.cpp @@ -0,0 +1,418 @@ +/* + 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. + */ + +#include "MultiPendulum.h" + +#include // TODO: Should I use another data structure? +#include + +#include "btBulletDynamicsCommon.h" +#include "LinearMath/btVector3.h" +#include "LinearMath/btAlignedObjectArray.h" +#include "../CommonInterfaces/CommonRigidBodyBase.h" +#include "../CommonInterfaces/CommonParameterInterface.h" + +static btScalar gPendulaQty = 2; //TODO: This would actually be an Integer, but the Slider does not like integers, so I floor it when changed + +static btScalar gDisplacedPendula = 1; //TODO: This is an int as well + +static btScalar gPendulaRestitution = 1; // Default pendulum restitution is 1 to restore all force + +static btScalar gSphereRadius = 1; // The sphere radius + +static btScalar gCurrentPendulumLength = 8; + +static btScalar gInitialPendulumLength = 8; // Default pendulum length (distance between two spheres) + +static btScalar gDisplacementForce = 30; // The default force with which we move the pendulum + +struct MultiPendulumExample: public CommonRigidBodyBase { + MultiPendulumExample(struct GUIHelperInterface* helper) : + CommonRigidBodyBase(helper) { + } + + virtual ~MultiPendulumExample() { + } + + virtual void initPhysics(); // build a multi pendulum + + virtual void renderScene(); // render the scene to screen + + virtual void createMultiPendulum(btSphereShape* colShape, + btScalar pendulaQty, btScalar xPosition, btScalar yPosition,btScalar zPosition, + btScalar length, btScalar mass); // create a multi pendulum at the indicated x and y position, the specified number of pendula formed into a chain, each with indicated length and mass + + virtual void changePendulaLength(btScalar length); // change the pendulum length + + virtual void changePendulaRestitution(btScalar restitution); // change the pendula restitution + + virtual void stepSimulation(float deltaTime); // step the simulation + + virtual bool keyboardCallback(int key, int state); // handle keyboard callbacks + + void resetCamera() { + 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]); + } + + std::vector constraints; // keep a handle to the slider constraints + + std::vector pendula; // keep a handle to the pendula +}; + +static MultiPendulumExample* mex = NULL; // Handle to the example to access it via functions. Do not use this in your simulation! + +void onMultiPendulaLengthChanged(float pendulaLength); // Change the pendula length + +void onMultiPendulaRestitutionChanged(float pendulaRestitution); // change the pendula restitution + +void floorMSliderValue(float notUsed); // floor the slider values which should be integers + +void MultiPendulumExample::initPhysics() { // Setup your physics scene + + { // create a slider to change the number of pendula + SliderParams slider("Number of Pendula", &gPendulaQty); + slider.m_minVal = 1; + slider.m_maxVal = 50; + slider.m_callback = floorMSliderValue; // hack to get integer values + slider.m_clampToNotches = false; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + { // create a slider to change the number of displaced pendula + SliderParams slider("Number of Displaced Pendula", &gDisplacedPendula); + slider.m_minVal = 0; + slider.m_maxVal = 49; + slider.m_callback = floorMSliderValue; // hack to get integer values + slider.m_clampToNotches = false; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + { // create a slider to change the pendula restitution + SliderParams slider("Pendula Restitution", &gPendulaRestitution); + slider.m_minVal = 0; + slider.m_maxVal = 1; + slider.m_clampToNotches = false; + slider.m_callback = onMultiPendulaRestitutionChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + { // create a slider to change the pendulum length + SliderParams slider("Pendula Length", &gCurrentPendulumLength); + slider.m_minVal = 0; + slider.m_maxVal = 49; + slider.m_clampToNotches = false; + slider.m_callback = onMultiPendulaLengthChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + { // create a slider to change the force to displace the lowest pendulum + SliderParams slider("Displacement force", &gDisplacementForce); + slider.m_minVal = 0.1; + slider.m_maxVal = 200; + slider.m_clampToNotches = false; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + m_guiHelper->setUpAxis(1); + + createEmptyDynamicsWorld(); + + // create a debug drawer + m_guiHelper->createPhysicsDebugDrawer(m_dynamicsWorld); + if (m_dynamicsWorld->getDebugDrawer()) + m_dynamicsWorld->getDebugDrawer()->setDebugMode( + btIDebugDraw::DBG_DrawWireframe + + btIDebugDraw::DBG_DrawContactPoints + + btIDebugDraw::DBG_DrawConstraints + + btIDebugDraw::DBG_DrawConstraintLimits); + + { // create the multipendulum starting at the indicated position below and where each pendulum has the following mass + btScalar pendulumMass(1.f); + + btScalar xPosition(0.0f); // initial top-most pendulum position + btScalar yPosition(15.0f); + btScalar zPosition(0.0f); + + // Re-using the same collision is better for memory usage and performance + btSphereShape* pendulumShape = new btSphereShape(gSphereRadius); + m_collisionShapes.push_back(pendulumShape); + + // create multi-pendulum + createMultiPendulum(pendulumShape, floor(gPendulaQty), xPosition, yPosition,zPosition, + gInitialPendulumLength, pendulumMass); + } + + m_guiHelper->autogenerateGraphicsObjects(m_dynamicsWorld); +} + +void MultiPendulumExample::stepSimulation(float deltaTime) { + if (m_dynamicsWorld) { + m_dynamicsWorld->stepSimulation(deltaTime); + } + +} + +void MultiPendulumExample::createMultiPendulum(btSphereShape* colShape, + btScalar pendulaQty, btScalar xPosition, btScalar yPosition, btScalar zPosition, + btScalar length, btScalar mass) { + + // The multi-pendulum looks like this (names when built): + //..........0......./.......1...../.......2......./..etc...:pendulum build iterations + // O parentSphere + // | + // O childSphere / parentSphere + // | + // O ............./ childSphere / parentSphere + // | + // O .........................../ childSphere + // etc. + + //create the top element of the pendulum + btTransform startTransform; + startTransform.setIdentity(); + + // position the top sphere + startTransform.setOrigin( + btVector3(btScalar(xPosition), btScalar(yPosition), btScalar(zPosition))); + + startTransform.setRotation(btQuaternion(0, 0, 0, 1)); // zero rotation + + btRigidBody* topSphere = createRigidBody(mass, startTransform, colShape); + + // disable the deactivation when object does not move anymore + topSphere->setActivationState(DISABLE_DEACTIVATION); + + //make top sphere position "fixed" in the world by attaching it to a the world with a point to point constraint + // The pivot is defined in the reference frame of topSphere, so the attachment should be exactly at the center of topSphere + btVector3 constraintPivot(0.0f, 0.0f, 0.0f); + btPoint2PointConstraint* p2pconst = new btPoint2PointConstraint( + *topSphere, constraintPivot); + + p2pconst->setDbgDrawSize(btScalar(5.f)); // set the size of the debug drawing + + // add the constraint to the world + m_dynamicsWorld->addConstraint(p2pconst, true); + + btRigidBody* parentSphere = topSphere; // set the top sphere as the parent sphere for the next sphere to be created + + for (int i = 0; i < pendulaQty; i++) { // produce the number of pendula + + // create joint element to make the pendulum rotate it + + // position the joint sphere at the same position as the top sphere + startTransform.setOrigin( + btVector3(btScalar(xPosition), btScalar(yPosition - length*(i)), + btScalar(0))); + + startTransform.setRotation(btQuaternion(0, 0, 0, 1)); // zero rotation + + btRigidBody* jointSphere = createRigidBody(mass, startTransform, + colShape); + jointSphere->setFriction(0); // we do not need friction here + + // disable the deactivation when object does not move anymore + jointSphere->setActivationState(DISABLE_DEACTIVATION); + + //create constraint between parentSphere and jointSphere + // this is represented by the constraint pivot in the local frames of reference of both constrained spheres + btTransform constraintPivotInParentSphereRF, constraintPivotInJointSphereRF; + + constraintPivotInParentSphereRF.setIdentity(); + constraintPivotInJointSphereRF.setIdentity(); + + // the orientation of a point-to-point constraint does not matter, as is has no rotational limits + + //Obtain the position of parentSphere in local reference frame of the jointSphere (the pivot is therefore in the center of parentSphere) + btVector3 parentSphereInJointSphereRF = + (jointSphere->getWorldTransform().inverse()( + parentSphere->getWorldTransform().getOrigin())); + constraintPivotInJointSphereRF.setOrigin(parentSphereInJointSphereRF); + + btPoint2PointConstraint* p2pconst = new btPoint2PointConstraint( + *parentSphere,*jointSphere,constraintPivotInParentSphereRF.getOrigin(), constraintPivotInJointSphereRF.getOrigin()); + + p2pconst->setDbgDrawSize(btScalar(5.f)); // set the size of the debug drawing + + // add the constraint to the world + m_dynamicsWorld->addConstraint(p2pconst, true); + + // create a slider constraint to change the length of the pendula while it swings + + startTransform.setIdentity(); // reset start transform + + // position the child sphere below the joint sphere + startTransform.setOrigin( + btVector3(btScalar(xPosition), btScalar(yPosition - length*(i+1)), + btScalar(0))); + + startTransform.setRotation(btQuaternion(0, 0, 0, 1)); // zero rotation + + btRigidBody* childSphere = createRigidBody(mass, startTransform, + colShape); + childSphere->setFriction(0); // we do not need friction here + pendula.push_back(childSphere); + + // disable the deactivation when object does not move anymore + childSphere->setActivationState(DISABLE_DEACTIVATION); + + //create slider constraint between jointSphere and childSphere + // this is represented by the constraint pivot in the local frames of reference of both constrained spheres + // furthermore we need to rotate the constraint appropriately to orient it correctly in space + btTransform constraintPivotInChildSphereRF; + + constraintPivotInJointSphereRF.setIdentity(); + constraintPivotInChildSphereRF.setIdentity(); + + // the orientation of a point-to-point constraint does not matter, as is has no rotational limits + + //Obtain the position of jointSphere in local reference frame of the childSphere (the pivot is therefore in the center of jointSphere) + btVector3 jointSphereInChildSphereRF = + (childSphere->getWorldTransform().inverse()( + jointSphere->getWorldTransform().getOrigin())); + constraintPivotInChildSphereRF.setOrigin(jointSphereInChildSphereRF); + + // the slider constraint is x aligned per default, but we want it to be y aligned, therefore we rotate it + btQuaternion qt; + qt.setEuler(0, 0, -SIMD_HALF_PI); + constraintPivotInJointSphereRF.setRotation(qt); //we use Y like up Axis + constraintPivotInChildSphereRF.setRotation(qt); //we use Y like up Axis + + btSliderConstraint* sliderConst = new btSliderConstraint(*jointSphere, + *childSphere, constraintPivotInJointSphereRF, constraintPivotInChildSphereRF, true); + + sliderConst->setDbgDrawSize(btScalar(5.f)); // set the size of the debug drawing + + // set limits + // the initial setup of the constraint defines the origins of the limit dimensions, + // therefore we set both limits directly to the current position of the parentSphere + sliderConst->setLowerLinLimit(btScalar(0)); + sliderConst->setUpperLinLimit(btScalar(0)); + sliderConst->setLowerAngLimit(btScalar(0)); + sliderConst->setUpperAngLimit(btScalar(0)); + constraints.push_back(sliderConst); + + // add the constraint to the world + m_dynamicsWorld->addConstraint(sliderConst, true); + parentSphere = childSphere; + } +} + +void MultiPendulumExample::changePendulaLength(btScalar length) { + btScalar lowerLimit = -gInitialPendulumLength; + for (std::vector::iterator sit = constraints.begin(); + sit != constraints.end(); sit++) { + btAssert((*sit) && "Null constraint"); + + // if the pendulum is being shortened beyond it's own length, we don't let the lower sphere to go past the upper one + if (lowerLimit <= length) { + (*sit)->setLowerLinLimit(length + lowerLimit); + (*sit)->setUpperLinLimit(length + lowerLimit); + } + } +} + +void MultiPendulumExample::changePendulaRestitution(btScalar restitution) { + for (std::vector::iterator rit = pendula.begin(); + rit != pendula.end(); rit++) { + btAssert((*rit) && "Null constraint"); + + (*rit)->setRestitution(restitution); + } +} + +void MultiPendulumExample::renderScene() { + CommonRigidBodyBase::renderScene(); +} + +bool MultiPendulumExample::keyboardCallback(int key, int state) { + + //b3Printf("Key pressed: %d in state %d \n",key,state); + + //key 1, key 2, key 3 + switch (key) { + case 49 /*ASCII for 1*/: { + + //assumption: Sphere are aligned in Z axis + btScalar newLimit = gCurrentPendulumLength + 0.1; + + changePendulaLength(newLimit); + gCurrentPendulumLength = newLimit; + + b3Printf("Increase pendulum length to %f", gCurrentPendulumLength); + return true; + } + case 50 /*ASCII for 2*/: { + + //assumption: Sphere are aligned in Z axis + btScalar newLimit = gCurrentPendulumLength - 0.1; + + //is being shortened beyond it's own length, we don't let the lower sphere to go over the upper one + if (0 <= newLimit) { + changePendulaLength(newLimit); + gCurrentPendulumLength = newLimit; + } + + b3Printf("Decrease pendulum length to %f", gCurrentPendulumLength); + return true; + } + case 51 /*ASCII for 3*/: { + for (int i = gPendulaQty-1; i >= gPendulaQty-gDisplacedPendula; i--) { + if (gDisplacedPendula >= 0 && gDisplacedPendula < gPendulaQty) + pendula[i]->applyCentralForce(btVector3(gDisplacementForce, 0, 0)); + } + return true; + } + } + + return false; +} + +// GUI parameter modifiers + +void onMultiPendulaLengthChanged(float pendulaLength) { // Change the pendula length + if (mex){ + mex->changePendulaLength(pendulaLength); + } + //b3Printf("Pendula length changed to %f \n",sliderValue ); + +} + +void onMultiPendulaRestitutionChanged(float pendulaRestitution) { // change the pendula restitution + if (mex){ + mex->changePendulaRestitution(pendulaRestitution); + } + +} + +void floorMSliderValue(float notUsed) { // floor the slider values which should be integers + gPendulaQty = floor(gPendulaQty); + gDisplacedPendula = floor(gDisplacedPendula); +} + +CommonExampleInterface* ET_MultiPendulumCreateFunc( + CommonExampleOptions& options) { + mex = new MultiPendulumExample(options.m_guiHelper); + return mex; +} diff --git a/examples/ExtendedTutorials/MultiPendulum.h b/examples/ExtendedTutorials/MultiPendulum.h new file mode 100644 index 000000000..336c1be3c --- /dev/null +++ b/examples/ExtendedTutorials/MultiPendulum.h @@ -0,0 +1,22 @@ +/* +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 ET_MULTI_PENDULUM_EXAMPLE_H +#define ET_MULTI_PENDULUM_EXAMPLE_H + +class CommonExampleInterface* ET_MultiPendulumCreateFunc(struct CommonExampleOptions& options); + + +#endif //ET_MULTI_PENDULUM_EXAMPLE_H diff --git a/examples/ExtendedTutorials/NewtonsCradle.cpp b/examples/ExtendedTutorials/NewtonsCradle.cpp new file mode 100644 index 000000000..76bbf6622 --- /dev/null +++ b/examples/ExtendedTutorials/NewtonsCradle.cpp @@ -0,0 +1,350 @@ +/* + 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. + */ + +#include "NewtonsCradle.h" + +#include // TODO: Should I use another data structure? +#include + +#include "btBulletDynamicsCommon.h" +#include "LinearMath/btVector3.h" +#include "LinearMath/btAlignedObjectArray.h" +#include "../CommonInterfaces/CommonRigidBodyBase.h" +#include "../CommonInterfaces/CommonParameterInterface.h" + +static btScalar gPendulaQty = 5; // Number of pendula in newton's cradle +//TODO: This would actually be an Integer, but the Slider does not like integers, so I floor it when changed + +static btScalar gDisplacedPendula = 1; // number of displaced pendula +//TODO: This is an int as well + +static btScalar gPendulaRestitution = 1; // pendula restition when hitting against each other + +static btScalar gSphereRadius = 1; // pendula radius + +static btScalar gCurrentPendulumLength = 8; // current pendula length + +static btScalar gInitialPendulumLength = 8; // default pendula length + +static btScalar gForcingForce = 30; // default force to displace the pendula + +struct NewtonsCradleExample: public CommonRigidBodyBase { + NewtonsCradleExample(struct GUIHelperInterface* helper) : + CommonRigidBodyBase(helper) { + } + virtual ~NewtonsCradleExample() { + } + virtual void initPhysics(); + virtual void renderScene(); + virtual void createPendulum(btSphereShape* colShape, btScalar xPosition, + btScalar yPosition, btScalar zPosition, btScalar length, btScalar mass); + virtual void changePendulaLength(btScalar length); + virtual void changePendulaRestitution(btScalar restitution); + virtual void stepSimulation(float deltaTime); + virtual bool keyboardCallback(int key, int state); + void resetCamera() { + 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]); + } + + std::vector constraints; + std::vector pendula; +}; + +static NewtonsCradleExample* nex = NULL; + +void onPendulaLengthChanged(float pendulaLength); + +void onPendulaRestitutionChanged(float pendulaRestitution); + +void floorSliderValue(float notUsed); + +void NewtonsCradleExample::initPhysics() { + + { // create a slider to change the number of pendula + SliderParams slider("Number of Pendula", &gPendulaQty); + slider.m_minVal = 1; + slider.m_maxVal = 50; + slider.m_callback = floorSliderValue; // hack to get integer values + slider.m_clampToNotches = false; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + { // create a slider to change the number of displaced pendula + SliderParams slider("Number of Displaced Pendula", &gDisplacedPendula); + slider.m_minVal = 0; + slider.m_maxVal = 49; + slider.m_callback = floorSliderValue; // hack to get integer values + slider.m_clampToNotches = false; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + { // create a slider to change the pendula restitution + SliderParams slider("Pendula Restitution", &gPendulaRestitution); + slider.m_minVal = 0; + slider.m_maxVal = 1; + slider.m_clampToNotches = false; + slider.m_callback = onPendulaRestitutionChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + { // create a slider to change the pendulum length + SliderParams slider("Pendula Length", &gCurrentPendulumLength); + slider.m_minVal = 0; + slider.m_maxVal = 49; + slider.m_clampToNotches = false; + slider.m_callback = onPendulaLengthChanged; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + { // create a slider to change the force to displace the lowest pendulum + SliderParams slider("Displacement force", &gForcingForce); + slider.m_minVal = 0.1; + slider.m_maxVal = 200; + slider.m_clampToNotches = false; + m_guiHelper->getParameterInterface()->registerSliderFloatParameter( + slider); + } + + m_guiHelper->setUpAxis(1); + + createEmptyDynamicsWorld(); + + // create a debug drawer + m_guiHelper->createPhysicsDebugDrawer(m_dynamicsWorld); + if (m_dynamicsWorld->getDebugDrawer()) + m_dynamicsWorld->getDebugDrawer()->setDebugMode( + btIDebugDraw::DBG_DrawWireframe + + btIDebugDraw::DBG_DrawContactPoints + + btIDebugDraw::DBG_DrawConstraints + + btIDebugDraw::DBG_DrawConstraintLimits); + + { // create the pendulum starting at the indicated position below and where each pendulum has the following mass + btScalar pendulumMass(1.f); + + btScalar xPosition(0.0f); // initial left-most pendulum position + btScalar yPosition(15.0f); + btScalar zPosition(0.0f); + + // Re-using the same collision is better for memory usage and performance + btSphereShape* pendulumShape = new btSphereShape(gSphereRadius); + m_collisionShapes.push_back(pendulumShape); + + for (int i = 0; i < floor(gPendulaQty); i++) { + + // create pendulum + createPendulum(pendulumShape, xPosition, yPosition,zPosition, + gInitialPendulumLength, pendulumMass); + + // displace the pendula 1.05 sphere size, so that they all nearly touch (small spacings in between + xPosition -= 2.1f * gSphereRadius; + } + } + + m_guiHelper->autogenerateGraphicsObjects(m_dynamicsWorld); +} + +void NewtonsCradleExample::stepSimulation(float deltaTime) { + if (m_dynamicsWorld) { + m_dynamicsWorld->stepSimulation(deltaTime); + } + +} + +void NewtonsCradleExample::createPendulum(btSphereShape* colShape, + btScalar xPosition, btScalar yPosition, btScalar zPosition, btScalar length, btScalar mass) { + + // The pendulum looks like this (names when built): + // O topSphere + // | + // O bottomSphere + + //create a dynamic pendulum + btTransform startTransform; + startTransform.setIdentity(); + + // position the top sphere above ground with a moving x position + startTransform.setOrigin( + btVector3(btScalar(xPosition), btScalar(yPosition), btScalar(zPosition))); + startTransform.setRotation(btQuaternion(0, 0, 0, 1)); // zero rotation + btRigidBody* topSphere = createRigidBody(mass, startTransform, colShape); + + // position the bottom sphere below the top sphere + startTransform.setOrigin( + btVector3(btScalar(xPosition), btScalar(yPosition - length), + btScalar(zPosition))); + + startTransform.setRotation(btQuaternion(0, 0, 0, 1)); // zero rotation + btRigidBody* bottomSphere = createRigidBody(mass, startTransform, colShape); + bottomSphere->setFriction(0); // we do not need friction here + pendula.push_back(bottomSphere); + + // disable the deactivation when objects do not move anymore + topSphere->setActivationState(DISABLE_DEACTIVATION); + bottomSphere->setActivationState(DISABLE_DEACTIVATION); + + bottomSphere->setRestitution(gPendulaRestitution); // set pendula restitution + + //make the top sphere position "fixed" to the world by attaching with a point to point constraint + // The pivot is defined in the reference frame of topSphere, so the attachment is exactly at the center of the topSphere + btVector3 constraintPivot(btVector3(0.0f, 0.0f, 0.0f)); + btPoint2PointConstraint* p2pconst = new btPoint2PointConstraint(*topSphere, + constraintPivot); + + p2pconst->setDbgDrawSize(btScalar(5.f)); // set the size of the debug drawing + + // add the constraint to the world + m_dynamicsWorld->addConstraint(p2pconst, true); + + //create constraint between spheres + // this is represented by the constraint pivot in the local frames of reference of both constrained spheres + // furthermore we need to rotate the constraint appropriately to orient it correctly in space + btTransform constraintPivotInTopSphereRF, constraintPivotInBottomSphereRF; + + constraintPivotInTopSphereRF.setIdentity(); + constraintPivotInBottomSphereRF.setIdentity(); + + // the slider constraint is x aligned per default, but we want it to be y aligned, therefore we rotate it + btQuaternion qt; + qt.setEuler(0, 0, -SIMD_HALF_PI); + constraintPivotInTopSphereRF.setRotation(qt); //we use Y like up Axis + constraintPivotInBottomSphereRF.setRotation(qt); //we use Y like up Axis + + //Obtain the position of topSphere in local reference frame of bottomSphere (the pivot is therefore in the center of topSphere) + btVector3 topSphereInBottomSphereRF = + (bottomSphere->getWorldTransform().inverse()( + topSphere->getWorldTransform().getOrigin())); + constraintPivotInBottomSphereRF.setOrigin(topSphereInBottomSphereRF); + + btSliderConstraint* sliderConst = new btSliderConstraint(*topSphere, + *bottomSphere, constraintPivotInTopSphereRF, constraintPivotInBottomSphereRF, true); + + sliderConst->setDbgDrawSize(btScalar(5.f)); // set the size of the debug drawing + + // set limits + // the initial setup of the constraint defines the origins of the limit dimensions, + // therefore we set both limits directly to the current position of the topSphere + sliderConst->setLowerLinLimit(btScalar(0)); + sliderConst->setUpperLinLimit(btScalar(0)); + sliderConst->setLowerAngLimit(btScalar(0)); + sliderConst->setUpperAngLimit(btScalar(0)); + constraints.push_back(sliderConst); + + // add the constraint to the world + m_dynamicsWorld->addConstraint(sliderConst, true); +} + +void NewtonsCradleExample::changePendulaLength(btScalar length) { + btScalar lowerLimit = -gInitialPendulumLength; + for (std::vector::iterator sit = constraints.begin(); + sit != constraints.end(); sit++) { + btAssert((*sit) && "Null constraint"); + + //if the pendulum is being shortened beyond it's own length, we don't let the lower sphere to go past the upper one + if (lowerLimit <= length) { + (*sit)->setLowerLinLimit(length + lowerLimit); + (*sit)->setUpperLinLimit(length + lowerLimit); + } + } +} + +void NewtonsCradleExample::changePendulaRestitution(btScalar restitution) { + for (std::vector::iterator rit = pendula.begin(); + rit != pendula.end(); rit++) { + btAssert((*rit) && "Null constraint"); + + (*rit)->setRestitution(restitution); + } +} + +void NewtonsCradleExample::renderScene() { + CommonRigidBodyBase::renderScene(); +} + +bool NewtonsCradleExample::keyboardCallback(int key, int state) { + //b3Printf("Key pressed: %d in state %d \n",key,state); + + //key 1, key 2, key 3 + switch (key) { + case 49 /*ASCII for 1*/: { + + //assumption: Sphere are aligned in Z axis + btScalar newLimit = gCurrentPendulumLength + 0.1; + + changePendulaLength(newLimit); + gCurrentPendulumLength = newLimit; + + b3Printf("Increase pendulum length to %f", gCurrentPendulumLength); + return true; + } + case 50 /*ASCII for 2*/: { + + //assumption: Sphere are aligned in Z axis + btScalar newLimit = gCurrentPendulumLength - 0.1; + + //is being shortened beyond it's own length, we don't let the lower sphere to go over the upper one + if (0 <= newLimit) { + changePendulaLength(newLimit); + gCurrentPendulumLength = newLimit; + } + + b3Printf("Decrease pendulum length to %f", gCurrentPendulumLength); + return true; + } + case 51 /*ASCII for 3*/: { + for (int i = 0; i < gDisplacedPendula; i++) { + if (gDisplacedPendula >= 0 && gDisplacedPendula <= gPendulaQty) + pendula[i]->applyCentralForce(btVector3(gForcingForce, 0, 0)); + } + return true; + } + } + + return false; +} + +// GUI parameter modifiers + +void onPendulaLengthChanged(float pendulaLength) { + if (nex){ + nex->changePendulaLength(pendulaLength); + //b3Printf("Pendula length changed to %f \n",sliderValue ); + } +} + +void onPendulaRestitutionChanged(float pendulaRestitution) { + if (nex){ + nex->changePendulaRestitution(pendulaRestitution); + } +} + +void floorSliderValue(float notUsed) { + gPendulaQty = floor(gPendulaQty); + gDisplacedPendula = floor(gDisplacedPendula); +} + +CommonExampleInterface* ET_NewtonsCradleCreateFunc( + CommonExampleOptions& options) { + nex = new NewtonsCradleExample(options.m_guiHelper); + return nex; +} diff --git a/examples/ExtendedTutorials/NewtonsCradle.h b/examples/ExtendedTutorials/NewtonsCradle.h new file mode 100644 index 000000000..9b9e2f247 --- /dev/null +++ b/examples/ExtendedTutorials/NewtonsCradle.h @@ -0,0 +1,22 @@ +/* +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 ET_NEWTONIAN_PENDULUM_EXAMPLE_H +#define ET_NEWTONIAN_PENDULUM_EXAMPLE_H + +class CommonExampleInterface* ET_NewtonsCradleCreateFunc(struct CommonExampleOptions& options); + + +#endif //ET_NEWTONIAN_PENDULUM_EXAMPLE_H