Model follows camera-look at position

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Model follows camera-look at position

Post by rogerborg »

This sample app makes a node (with a mesh) attempt try and stay in front of the camera (a FPS camera in this example). The node rotates itself and moves towards a point some distance in front of the camera, bounded by collision with the world. The position that it's moving towards is shown with a billboard.

Demonstrates:
  • Sizing a collision ellipse based on a mesh size.
  • Collision animators on a model-node and a camera.
  • Collision rays.
  • Basic trig: find direction to a point, rotate towards that point, move towards that point.
  • Framerate independent movement.

Code: Select all

#include <irrlicht.h>

using namespace irr;

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

int main()
{
    IrrlichtDevice * device =
        createDevice(video::EDT_OPENGL, core::dimension2d<s32>(640, 480), 32, false);
        
    video::IVideoDriver* driver = device->getVideoDriver();
    scene::ISceneManager* smgr = device->getSceneManager();

    device->getCursorControl()->setVisible(false);

    // Load a Quake level and create a triangle selector
    device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
    scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
    scene::ISceneNode* q3node = smgr->addOctTreeSceneNode(q3levelmesh->getMesh(0));
    q3node->setPosition(core::vector3df(-1350,-90,-1400));
    scene::ITriangleSelector* selector = 0;
    selector = smgr->createOctTreeTriangleSelector(q3levelmesh->getMesh(0), q3node, 128);
    q3node->setTriangleSelector(selector);

    // For simplicity, use the sample FPS camera.  In a real app, you'd use a 
    // dumb camera and do the movement logic for the camera yourself.
    scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
    camera->setPosition(core::vector3df(-100, 30, -150));

    // add a billboard to show the target movement position
    scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();
    bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
    bill->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
    bill->setMaterialFlag(video::EMF_LIGHTING, false);
    bill->setMaterialFlag(video::EMF_ZBUFFER, false); // Always appears on top
    bill->setSize(core::dimension2d<f32>(20.0f, 20.0f));


    // Create a 3rd person player node
    scene::ISceneNode * player = smgr->addEmptySceneNode();
    player->setPosition(core::vector3df(-70,0,-90));

    // The faerie model is oriented towards +X, but I do calculations on +Z
    // so I have to use a child node to rotate it 90 degrees anticlockwise
    scene::IAnimatedMesh* faerieMesh = smgr->getMesh("../../media/faerie.md2");
    scene::IAnimatedMeshSceneNode * visualNode = smgr->addAnimatedMeshSceneNode(faerieMesh, player);
    video::SMaterial material;
    material.setTexture(0, driver->getTexture("../../media/faerie2.bmp"));
    material.Lighting = true;
    visualNode->getMaterial(0) = material;

    visualNode->setRotation(core::vector3df(0.f, -90.f, 0.f)); // rotates +X to +Z
    
    // Work out how big the faerie mesh is
    const core::aabbox3df & box = visualNode->getBoundingBox();
    const f32 height = (box.MaxEdge.Y - box.MinEdge.Y) / 2.f;
    const f32 verticalOffset = -box.MinEdge.Y - box.MaxEdge.Y;
    const f32 waist = core::max_(box.MaxEdge.X - box.MinEdge.X, box.MaxEdge.Z - box.MinEdge.Z) / 2.f;

    // And give the avatar node an appropriate physical presence
    scene::ISceneNodeAnimator * anim = smgr->createCollisionResponseAnimator(
        selector, player, core::vector3df(waist, height, waist),
        core::vector3df(0, -3, 0), 
        core::vector3df(0, verticalOffset, 0));
    player->addAnimator(anim);
    anim->drop(); anim = 0;

    // Give the camera a physical presence that's thin and taller than the faerie
    anim = smgr->createCollisionResponseAnimator(
        selector, camera, core::vector3df(5, height * 2.f, 5),
        core::vector3df(0,-3,0), 
        core::vector3df(0,0,0));
    camera->addAnimator(anim);
    anim->drop(); anim = 0;

    selector->drop(); selector = 0;

    smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
        video::SColorf(1.0f,1.0f,1.0f,1.0f),
        600.0f);

    bool wasRunning = false; // Used to control animatins
    u32 lastFrameTime = device->getTimer()->getTime();

    // These values control how the avatar moves
    const f32 TURN_SPEED = 200.f;
    const f32 MOVE_SPEED = 100.f;
    const f32 STOP_AT_DISTANCE_FROM_TARGET = waist * 1.1f;
    const f32 MAXIMUM_YAW_DELTA_BEFORE_MOVING = 60.f; // degrees
    const f32 MAX_TARGET_POSITION_IN_FRONT_OF_CAMERA = waist * 10.f;

    while(device->run())
    if (device->isWindowActive())
    {
        u32 thisFrameDuration = device->getTimer()->getTime() - lastFrameTime;
        f32 frameTimeMultiplier = (f32)thisFrameDuration / 1000.f;
        lastFrameTime += thisFrameDuration;

        // Create a limited length ray in front of the camera
        core::line3d<f32> line;
        line.start = camera->getPosition();
        line.end = line.start + 
                    (camera->getTarget() - line.start).normalize()
                        * MAX_TARGET_POSITION_IN_FRONT_OF_CAMERA;

        core::vector3df targetPosition;
        core::triangle3df tri;

        // Test if the ray hits the world. If it does, move towards that position.
        if (!smgr->getSceneCollisionManager()->getCollisionPoint(line,
                                                                 q3node->getTriangleSelector(),
                                                                 targetPosition,
                                                                 tri))
        {
            // Didn't hit the world, so move towards the end of the ray.
            targetPosition = line.end;
        }

        // Show where the target position is.
        bill->setPosition(targetPosition);

        // See how far the avatar is from the target node
        core::vector3df toTarget(targetPosition - player->getAbsolutePosition());
        toTarget.Y = 0; // Ignore any vertical difference

        if(toTarget.getLength() <= STOP_AT_DISTANCE_FROM_TARGET)
        {
            // The avatar is close to the target position, so don't move.
            if(wasRunning)
            {
                visualNode->setMD2Animation(scene::EMAT_STAND);
                wasRunning = false;
            }
        }
        else
        {
            // Turn, and if facing near enough to the right direction, move.

            // Work out the required rotation, in degrees
            f32 requiredYaw = atan2(toTarget.Z, toTarget.X) * core::RADTODEG;
            // atan2 calculates anticlockwise from the X axis.  We want clockwise from the Z axis.
            requiredYaw *= -1.f; // Make the result clockwise...
            requiredYaw += 90.f; // ...and rebase from X axis to Z axis.

            f32 actualYaw = player->getRotation().Y;

            // Work out how much the avatar would have to turn
            f32 deltaYaw = requiredYaw - actualYaw;
            if(deltaYaw > 180.f)
                deltaYaw = deltaYaw - 360.f;
            else if(deltaYaw < -180.f)
                deltaYaw = deltaYaw + 360.f;

            // Is it s facing close enough to the right direction in order to move?
            bool move = fabs(deltaYaw) < MAXIMUM_YAW_DELTA_BEFORE_MOVING;

            // Now limit the rate of turn.
            if(deltaYaw > TURN_SPEED * frameTimeMultiplier)
                deltaYaw = TURN_SPEED * frameTimeMultiplier;
            else if(deltaYaw < -TURN_SPEED * frameTimeMultiplier)
                deltaYaw = -TURN_SPEED * frameTimeMultiplier;

            // Do the actual rotation.
            actualYaw += deltaYaw;
            player->setRotation(core::vector3df(0.f, actualYaw, 0.f));
            
            if(move)
            {
                if(!wasRunning)
                {
                    visualNode->setMD2Animation(scene::EMAT_RUN);
                    wasRunning = true;
                }

                actualYaw *= core::DEGTORAD; // Back to radians for sin/cos
                core::vector3df deltaMove(sin(actualYaw), 0.f, cos(actualYaw));
                deltaMove *= MOVE_SPEED * frameTimeMultiplier;
                player->setPosition(player->getAbsolutePosition() + deltaMove);
            }
        }

        driver->beginScene(true, true, 0);
        smgr->drawAll();
        driver->endScene();
    }

    device->drop(); device = 0;
    
    return 0;
}
Last edited by rogerborg on Sun Feb 24, 2008 9:32 am, edited 2 times in total.
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
Frosty Topaz
Posts: 107
Joined: Sat Nov 04, 2006 9:42 pm

Post by Frosty Topaz »

Very cool. Though it took me a little bit to figure out what you're doing. (At first I thought you meant to have a node which always has the same position/rotation relative to the camera, which would make no sense whatsoever)

Could be useful for RTSs, and NWN style RPGs.
Frosty Topaz
=========
It isn't easy being ice-encrusted aluminum silicate fluoride hydroxide...
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Post by rogerborg »

Hmm, it's not really a very common idiom. It's a misinterpretation of gobl1n's requirements for his 3rd person camera. I just didn't feel minded to throw the code away. ;)
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
Post Reply