Basic physics

As you might remember, in tutorial 1.3 we created an application in which we could go through the floor and the sculpture. It's a rather serious issue and it's time to see how to have it properly solved in nGENE. It's quite simple actually but needs introducing of physics wrapper.

The reason why we could pass through the floor and scene objects was that we hadn't set up collisions at all. Collision detection is one of the basics of all physics engines. nGENE uses one of the best - nVidia PhysX and therefore it's done very efficiently.

In this part we will see how to use character controllers to collide with the floor and scene objects and we will also add some physics actor programatically to help understand some concepts concerning them. You will see that physics is much more than "simple" collisions.

In this tutorial we will continue working on the code from tutorial 1.3 so it is suggested to read it carefully before going on with this part.

Firstly, the App class definition:

#pragma once
 
#include "FrameworkWin32.h"
#include "ScenePartitionerQuadTree.h"
 
#include "MyInputListener.h"
 
using namespace nGENE::Application;
using nGENE::ScenePartitionerQuadTree;
using nGENE::CharacterController;
 
class App: public FrameworkWin32
{
private:
    ScenePartitionerQuadTree* m_pPartitioner;
 
    MyInputListener* m_pInputListener;
 
    CharacterController* m_pController;
 
public:
    App() {}
    ~App() {}
 
    void createApplication();
 
    CharacterController* getController() const {return m_pController;}
};

The only change is adding m_pController member and accessor for it. m_pController is CharacterController what will be covered later.

However, much more changes are in the App.cpp file. The code below has been added at the end of App::createApplication() method:

PhysicsWorld* pWorld = Physics::getSingleton().createWorld(L"MyPhysicsWorld");

PhysicsMaterial* physMat = pWorld->createPhysicsMaterial(L"DefaultMaterial");
physMat->setRestitution(0.5f);
physMat->setDynamicFriction(0.5f);
physMat->setStaticFriction(0.5f);

pWorld->setGravity(Vector3(0.0f, -9.8f, 0.0f));

PhysicsActor* pActor = pWorld->createPhysicsActorMesh(pSculp->getScale(), 
    *pSculp->getSurface(L"surface_2")->getVertexBuffer());
pActor->attachNode(pSculp);
pActor->setShapeMaterial(0, *physMat);

pActor = pWorld->createPhysicsActor(L"Box", Vector3(100.0f, 0.5f, 100.0f));
pActor->attachNode(pBox);
pActor->setShapeMaterial(0, *physMat);
pWorld->addPhysicsActor(L"Plane", pActor);

CONTROLLER_DESC charDesc;
charDesc.height = 2.0f;
charDesc.obstacleLimit = 0.7f;
charDesc.radius = 0.8f;
m_pController = pWorld->createController(charDesc);
m_pController->attachNode(cam);
pWorld->addController(L"Player", m_pController);

Let's break it into smaller pieces:

PhysicsWorld* pWorld = Physics::getSingleton().createWorld(L"MyPhysicsWorld");

This code creates physical world with the name "MyPhysicsWorld" which will be used to identify it. Physical world is equivalent of a 3D scene in terms of physics. 3D scene describes the way the objects are presented on the screen and physical world describes the way they behave. Physics singleton class is a most important class of wrapper for PhysX.
PhysicsMaterial* physMat = pWorld->createPhysicsMaterial(L"DefaultMaterial");
physMat->setRestitution(0.5f);
physMat->setDynamicFriction(0.5f);
physMat->setStaticFriction(0.5f);
 
pWorld->setGravity(Vector3(0.0f, -9.8f, 0.0f));

Then we're creating physical material. Again there is full analogy with Materials. We set some attributes like restitution and friction to describe how objects with this material assigned would behave. Try changing this values to see the changes in objects behaviour. We will cover physical materials in more detail in later parts of the tutorial.

Then setGravity() method is called. By passing Vector3 to it we specify the gravity vector. In this case we use one specific to Earth's ground. By default vector(0, -9.8, 0) is used so in this case it's obsolete (but presents the method).

PhysicsActor* pActor = pWorld->createPhysicsActorMesh(pSculp->getScale(), 
    *pSculp->getSurface(L"surface_2")->getVertexBuffer());
pActor->attachNode(pSculp);
pActor->setShapeMaterial(0, *physMat);

Then we create physics actor by calling PhysicsWorld::createPhysicsActorMesh() method. Physics actor, to put is simple, is the body in which distance between any two points stays unchanged when forces are applied. The method takes scale and VertexBuffer object reference as arguments, so we pass ones specific to sculpture object. Then we attach pSculp node (our sculpture) and set material we created while back.

Then we create ground box but let's skip this code as it's almost nothing different.

Finally we create a character controller:

CONTROLLER_DESC charDesc;
charDesc.height = 2.0f;
charDesc.obstacleLimit = 0.7f;
charDesc.radius = 0.8f;
m_pController = pWorld->createController(charDesc);
m_pController->attachNode(cam);
pWorld->addController(L"Player", m_pController);

We specify its height, the height of the obstacle on which controller can climb and controller's radius. Then we create it will a call to the PhysicsWorld::createController() method. Similarly to physics actor we can attach any node to a character controller. As we want our camera not to pass through the floor, we attach it and eventually add controller to the world.

Now we will see how to add physics actors on run-time. When user will press "Q" key a new box will appear just above the sculpture and due to gravity will start to fall.
First of all, we add this member to MyInputListener class definition. It will specify how many physics actors have been added to the scene:

long m_BoxesNum;

And we change MyInputListener.cpp source file like that:

void MyInputListener::handleEvent(const MouseEvent &_evt)
{
    float fDiff = float(Engine::getSingleton().getTimer().getMilliseconds() - m_prev) * 0.001f;

    CharacterController* pController = m_pApp->getController();
    if(_evt.type == MET_MOVE_X || _evt.type == MET_MOVE_Y)
    {
        if(_evt.type == MET_MOVE_X)
            m_Angle1 += _evt.fvalue;
        else if(_evt.type == MET_MOVE_Y)
            m_Angle2 += _evt.fvalue;

        Quaternion quatRot1(Vector3(0.0f, 1.0f, 0.0f), m_Angle1);
        Quaternion quatRot2(Vector3(1.0f, 0.0f, 0.0f), m_Angle2);

        pController->setRotation(quatRot1 * quatRot2);
    }

    m_prev = Engine::getSingleton().getTimer().getMilliseconds();
}

void MyInputListener::handleEvent(const KeyboardEvent& _evt)
{
    if(_evt.isKeyPressed(KC_ESCAPE))
    {
        m_pApp->closeApplication();
        return;
    }

    if(_evt.isKeyPressed(KC_Q))
    {
        PhysicsWorld* pWorld = Physics::getSingleton().getWorld(L"MyPhysicsWorld");

        MaterialLibrary* pLibrary = MaterialManager::getSingleton().getLibrary(L"default");
        Material* pMaterial = pLibrary->getMaterial(L"normalMap");

        wostringstream buffer;
        buffer << L"Box_" << m_BoxesNum++;

        SceneManager* sm = Engine::getSingleton().getSceneManager(0);
        PrefabBox* pBox = sm->createBox(0.5f, 0.5f, 0.5f);
        pBox->setPosition(0.0f, 7.0f, 7.0f);
        Surface* pSurface = pBox->getSurface(L"Base");
        pSurface->setMaterial(pMaterial);
        sm->getRootNode()->addChild(buffer.str(), pBox);

        PhysicsActor* pActor = pWorld->createPhysicsActor(L"Box", Vector3(0.5, 0.5f, 0.5f), PhysicsActor::BT_DYNAMIC);
        pActor->attachNode(pBox);
        pActor->setShapeMaterial(0, *pWorld->getPhysicsMaterial(L"DefaultMaterial"));
        pWorld->addPhysicsActor(buffer.str(), pActor);
    }

    Vector3 movement;
    if(_evt.isKeyDown(KC_UP))
        movement.z = 1.0f;
    if(_evt.isKeyDown(KC_DOWN))
        movement.z = -1.0f;
    if(_evt.isKeyDown(KC_LEFT))
        movement.x = -1.0f;
    if(_evt.isKeyDown(KC_RIGHT))
        movement.x = 1.0f;

    if(movement.x != 0.0f || movement.z != 0.0f)
        m_pApp->getController()->move(movement);
}

As you can see the only changes in this file is changing accesses to the Camera object into CharacterController. It is because now we move the character controller. As it moves all nodes attached to it, move as well. Adding of physics actor is no different from adding physics actors earlier.

We compile and run:

Tut_1_6_1.jpg

Now it's much better! From the screenshot above it's clearly visible that collisions are working properly. You cannot also go through the floor or any of the objects.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License