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;
}