Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | irr::scene::CSceneNodeAnimatorCameraTrackball::CSceneNodeAnimatorCameraTrackball(
irr::video::IVideoDriver *driver,
irr::gui::ICursorControl *cursorControl,
irr::f32 radius,
irr::f32 moveSpeed,
irr::f32 rotateSpeed)
: m_driver(driver),
m_cursorControl(cursorControl),
m_radius(radius),
m_moveSpeed(moveSpeed),
m_rotateSpeed(rotateSpeed),
m_isPressed(false),
m_wheel(false)
{
m_rotation = irr::core::vector3df(1.0f,1.0f,0.0f);
m_cursorPos = m_cursorControl->getRelativePosition();
m_cursorMove = irr::core::position2d<irr::f32>(0,0);
}
|
Rien de bien affolant pour le constructeur. On se contente de passer tous les arguments aux attributs respectifs, puis on initialise la position du curseur et l'attribut qui contient les changements de cette position. Notez tout de même qu'on place des valeurs arbitraires dans le vecteur
m_rotation. Nous reviendrons dessus plus loin.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 | #define MAXANGLE 1.3
bool irr::scene::CSceneNodeAnimatorCameraTrackball::OnEvent(const SEvent& event)
{
if(event.EventType == EET_MOUSE_INPUT_EVENT) {
if (event.MouseInput.Event == irr::EMIE_LMOUSE_PRESSED_DOWN) {
m_isPressed = true;
return true;
}
else if (event.MouseInput.Event == irr::EMIE_LMOUSE_LEFT_UP) {
m_isPressed = false;
return true;
}
// change the radius/distance according to wheel moves
else if (event.MouseInput.Event == irr::EMIE_MOUSE_WHEEL) {
if ( event.MouseInput.Wheel > 0 ) { m_radius -= 1.0f * m_moveSpeed; }
else { m_radius += 1.0f * m_moveSpeed; }
m_wheel = true;
return true;
}
else if (event.MouseInput.Event == EMIE_MOUSE_MOVED) {
// if the mouse moves without the left button is pressed,
// we just update the position. Camera doesn't move
if (m_isPressed == false) {
m_cursorPos = m_cursorControl->getRelativePosition();
return true;
}
// if the left button is pressed, then we get the change and
// set the rotation vector. And update the position
else if (m_isPressed == true) {
m_cursorMove = m_cursorControl->getRelativePosition() - m_cursorPos;
m_rotation.X += m_cursorMove.X * m_rotateSpeed;
m_rotation.Y += m_cursorMove.Y * m_rotateSpeed;
// check the Y change to prevent vertical looping
if (m_rotation.Y >= MAXANGLE) { m_rotation.Y = MAXANGLE; }
else if (m_rotation.Y <= -MAXANGLE) { m_rotation.Y = -MAXANGLE; }
m_cursorPos = m_cursorControl->getRelativePosition();
return true;
}
}
}
return false; // event not proceeded
}
|
Voilà une fonction bien plus intéressante. Les lignes 7 à 14 servent juste à paramétrer le booléen
m_isPressed qui permet de savoir si le clic gauche de la souris est actuellement enfoncé. Les lignes 15 à 21 permettent de modifier le radius de la sphère, donc la distance entre la caméra et l'origine du repère en fonction des mouvements de la molette de la souris. C'est à ce moment que
m_moveSpeed est pris en compte pour modifier la "vitesse de déplacement". Notez au passage que le booléen
m_wheel passe à 1 au moindre changement. Nous verrons pourquoi dans la prochaine fonction.
Lignes 25 à 28, on se contente de mettre à jour notre version de la position du curseur si le booléen
m_isPressed est faux, pour éviter de prendre en compte un changement non voulu. Et enfin les dernières lignes servent à effectuer les changements en cas de déplacement de la souris alors que le clic gauche est enfoncé. Ces changements sont stockés dans le vecteur 3D
m_rotation. Petite subtilité toutefois, nous vérifions lignes 36 et 37 que le changement à appliquer à l'angle vertical n'est pas supérieur à une certaine valeur (basée sur la moitié de PI si vous voulez tout savoir) pour éviter d'éventuels looping verticaux qui mettraient à mal notre manière de calculer les coordonnées absolues de la caméra. Là encore, un coefficient (
m_rotateSpeed) est pris en compte et permet de déterminer la "vitesse de rotation" de la caméra grâce à un argument du constructeur.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | void irr::scene::CSceneNodeAnimatorCameraTrackball::animateNode(ISceneNode* node, u32 timeMs)
{
// check the node is a camera
if (!node || node->getType() != ESNT_CAMERA) { return; }
ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);
irr::scene::ISceneManager * sceneManager = camera->getSceneManager();
// check the camera is operational
if(!camera->isInputReceiverEnabled()) { return; }
if(sceneManager && sceneManager->getActiveCamera() != camera) { return; }
// if there is change to apply
if (m_isPressed || m_wheel)
{
irr::f32 posZ = m_radius * cos(m_rotation.Y);
irr::f32 posY = m_radius * sin(m_rotation.Y);
irr::core::vector3df camPos(irr::core::vector3df(0.0f, posY, posZ));
camPos.rotateXZBy(-m_rotation.X*40, irr::core::vector3df(0.0f, 0.0f, 0.0f));
camera->setPosition(camPos);
m_wheel = false;
}
}
|
Et voici
la fonction qu'il faut absolument comprendre. Les premières lignes n'ont rien de bien sensationnel, on se contente de récupérer un accès au node qu'on anime, donc à la caméra en ce qui nous concerne, et on vérifie que tout est OK. Si c'est le cas on arrive ligne 12 au moment de vérifier que la position de la caméra a bougé. C'est là qu'on voit l'utilité de
m_wheel que nous avons vu plus haut. Dès qu'un mouvement sur la molette est repéré, ce booléen passe à 1, puis à la fin de cette fonction quand nous aurons mis la position de la caméra à jour, il repassera à 0.
Si la condition est vérifiée donc, on commence par récupérer un vecteur indiquant la position de la caméra sur le plan ZY (lignes 14/15/17). Cette étape correspond exactement au calcul du vecteur
r rouge grâce à l'angle

sur le
schéma de Kayl. Au détail près que les axes Z et Y sont inversés, puisque lui définit Z comme étant la verticale alors que c'est l'axe Y qui l'est par défaut dans Irrlicht. C'est lors de cette étape que le rayon de la sphère, donc la distance entre la caméra et l'origine du repère est pris en compte (lignes 14/15).
Une fois qu'on a ce vecteur (qui représente directement des coordonnées absolues puisqu'on part du principe que l'origine du repère est 0,0,0) il ne nous reste plus qu'à effectuer une rotation d'angle

(toujours par rapport
à ce schéma) autour de l'axe vertical (Y pour nous), et nous obtiendrons les coordonnées absolues définitives de la caméra. C'est la ligne 18 qui s'en charge grâce à une fonction interne d'Irrlicht concernant les vecteurs. On aurait également pu passer par une matrice, mais autant utiliser tout ce qui est à notre disposition.
Utilisation
Comme le scene manager ne contient pas de fonction toute faite pour ajouter une caméra avec le bon animator (à moins que vous le modifiiez), nous allons devoir le faire nous même. Cela n'a vraiment rien de sorcier, regardez plutôt le code ci-dessous :
Code : C++ | irr::scene::ICameraSceneNode *camera = sceneManager->addCameraSceneNode(sceneManager->getRootSceneNode(),
irr::core::vector3df(40.0f, 100.0f, 40.0f), irr::core::vector3df(0.0f, 0.0f, 0.0f));
irr::scene::ISceneNodeAnimator* animator = new irr::scene::CSceneNodeAnimatorCameraTrackball(
driver, device->getCursorControl(), 50, 4, 6);
camera->addAnimator(animator);
animator->drop();
|
Nous commençons par ajouter une caméra banale, donc statique ligne 1. Puis on crée manuellement l'animator ligne 3, avant de l'attacher à la caméra ligne 6.