Tutoriel 14 - Contrôle de la caméra - 1ère partie

Par OGLdev, traduit par DragonJoker

Introduction

Ce tutoriel initie au contrôle de la caméra par l'utilisateur. Il se focalise sur le contrôle au clavier.

Contexte

Dans le tutoriel précédent, nous avions vu comment placer la caméra n'importe où dans le monde 3D. L'étape suivante est logiquement de permettre à l'utilisateur de la contrôler. Les mouvements ne seront pas restreints : l'utilisateur sera capable de bouger dans toutes les directions. Le contrôle de la caméra sera effectué en utilisant deux périphériques d'entrée : le clavier pour contrôler la position et la souris pour changer notre point de vue. C'est similaire à ce que la plupart des « First Person Shooter » font. Ce tutoriel va se concentrer sur le clavier et le suivant se focalisera sur la souris.

Nous allons gérer les quatre flèches directionnelles de manière conventionnelle. Rappelez-vous que notre transformation de caméra est définie par la position, par le vecteur cible et par le vecteur haut. Quand nous nous déplaçons en utilisant le clavier, nous changeons uniquement la position. Nous ne pouvons pas incliner la caméra, ni la tourner ; donc les vecteurs cible et haut restent inchangés.

Pour contrôler le clavier, nous utilisons une autre fonction de la bibliothèque GLUT : glutSpecialFunc(). Cette fonction enregistre un callback qui est appelé quand une touche « spéciale » est appuyée. Les touches spéciales comprennent : les touches F1 à F12, les flèches directionnelles, Page Haut, Page Bas, Origine, Fin et Insert. Si vous voulez récupérer les touches classiques (caractères et numériques), utilisez glutKeyboardFunc().

Explication du code

Les fonctionnalités de la caméra sont encapsulées dans la classe Camera. Cette classe regroupe les propriétés de la caméra et peut les modifier en fonction des événements de mouvement qu'elle reçoit. Les propriétés sont récupérées par la classe Pipeline qui génère la matrice de transformation basée dessus.

class Camera
{
public:

	Camera();
	Camera(const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up);
	bool OnKeyboard(int Key);
	const Vector3f& GetPos() const
	const Vector3f& GetTarget() const
	const Vector3f& GetUp() const

private:

	Vector3f m_pos;
	Vector3f m_target;
	Vector3f m_up;
};

Ceci est la déclaration de la classe Camera. Elle stocke les trois propriétés définissant la caméra : les vecteurs position, cible et haut. Deux constructeurs sont disponibles. Celui par défaut place simplement la caméra à l'origine, regardant le long de l'axe Z positif, avec un vecteur haut pointant vers le ciel (0, 1, 0). Il est aussi possible de créer la caméra avec des propriétés spécifiques. La fonction OnKeyboard() transmet les événements clavier à la caméra. Elle retourne un booléen qui indique si l'événement a été consommé par la classe. Si la touche est appropriée (une des flèches directionnelles), la valeur de retour est true, sinon false. De cette manière, vous pouvez construire une chaîne de clients qui reçoivent les événements clavier et vous arrêter après le premier qui fait réellement quelque chose de cet événement.

bool Camera::OnKeyboard(int Key)
{
	bool Ret = false;

	switch (Key)
	{
		case GLUT_KEY_UP:
		{
			m_pos += (m_target * StepSize);
			Ret = true;
		}
		break;

		case GLUT_KEY_DOWN:
		{
			m_pos -= (m_target * StepSize);
			Ret = true;
		}
		break;

		case GLUT_KEY_LEFT:
		{
			Vector3f Left = m_target.Cross(m_up);
			Left.Normalize();
			Left *= StepSize;
			m_pos += Left;
			Ret = true;
		}
		break;

		case GLUT_KEY_RIGHT:
		{
			Vector3f Right = m_up.Cross(m_target);
			Right.Normalize();
			Right *= StepSize;
			m_pos += Right;
			Ret = true;
		}
		break;
	}

	return Ret;
}

Cette fonction déplace la caméra en fonction des événements clavier. GLUT définit des macros correspondant aux flèches directionnelles, c'est ce sur quoi l'instruction « switch » est basée. Malheureusement ces macros sont de simples « int » au lieu d'une énumération.

Les mouvements avant et arrière sont les plus simples. Comme le mouvement est toujours le long du vecteur cible, nous avons seulement besoin d'ajouter ou de soustraire celui-ci de la position. Le vecteur cible lui-même reste inchangé. Notez qu'avant d'ajouter ou de soustraire le vecteur cible, nous le multiplions par une valeur constante appelée StepSize. Nous faisons cela pour chaque flèche directionnelle. StepSize fournit un point unique pour modifier la vitesse de déplacement (plus tard nous envisagerons de la transformer en propriété de classe). Pour rendre StepSize constante, nous devons nous assurer de toujours la multiplier à des vecteurs unitaires (c'est-à-dire que nous devons nous assurer que les vecteurs cible et haut sont toujours unitaires).

Le déplacement sur le côté est un peu plus complexe. Il est défini comme un mouvement le long d'un vecteur orthogonal au plan créé par les vecteurs cible et haut. Ce plan divise l'espace 3D en deux parties. Il y a deux vecteurs qui y sont orthogonaux et ils sont opposés l'un à l'autre. Nous pouvons appeler l'un « gauche » et l'autre « droite ». Ils sont générés en utilisant un produit vectoriel des vecteurs cible et haut dans les deux combinaisons possibles : cible fois haut ou haut fois cible (le produit vectoriel n'est pas une opération commutative : changer l'ordre des paramètres génère un résultat différent). Après avoir récupéré le vecteur gauche/droite, nous le normalisons, le multiplions par StepSize et l'ajoutons au vecteur position (ce qui le déplace vers la gauche ou la droite). Là aussi, les vecteurs cible et haut sont inchangés.

Notez que les opérations de cette fonction utilisent quelques nouveaux opérateurs tels que += et -= qui ont été ajoutés à la classe Vector3f.

static void InitializeGlutCallbacks()
{
	glutDisplayFunc(RenderSceneCB);
	glutIdleFunc(RenderSceneCB);
	glutSpecialFunc(SpecialKeyboardCB);
}

Ici, nous enregistrons un nouveau callback afin de traiter les événements clavier spéciaux. Le callback reçoit la touche et la position de la souris au moment où la touche est appuyée. Nous ignorons la position de la souris et donnons l'événement à une instance de la classe caméra allouée dans la section globale du fichier.

p.SetCamera(GameCamera.GetPos(), GameCamera.GetTarget(), GameCamera.GetUp());

Précédemment, nous avions initialisé les paramètres de la caméra dans la classe Pipeline en utilisant des vecteurs codés en dur. Maintenant nous avons supprimé ces vecteurs et nous utilisons les propriétés de la caméra directement à partir de la classe Camera.

Remerciements

Merci à Etay Meiri de me permettre de traduire ses tutoriels.

Résultat :
resultat

Article d'origine