#include // Horloges #include // Module Graphique #include // Module de rendu #include // Module utilitaire #include int main() { // Pour commencer, nous initialisons le module Graphique, celui-ci va provoquer l'initialisation (dans l'ordre), // du noyau (Core), Utility, Renderer. // NzInitializer est une classe RAII appelant Initialize dans son constructeur et Uninitialize dans son destructeur. // Autrement dit, une fois ceci fait nous n'avons plus à nous soucier de la libération du moteur. NzInitializer nazara; if (!nazara) { // Une erreur s'est produite dans l'initialisation d'un des modules std::cout << "Failed to initialize Nazara, see NazaraLog.log for further informations" << std::endl; std::getchar(); // On laise le temps de voir l'erreur return EXIT_FAILURE; } // Nazara étant initialisé, nous pouvons créer la scène // Une scène représente tout ce qui est visible par une ou plusieurs caméras. // La plupart du temps vous n'aurez pas besoin de plus d'une scène, mais cela peut se révéler utile pour mieux // organiser et optimiser le rendu. // Par exemple, une pièce contenant une télévision, laquelle affichant des images provenant d'une NzCamera // Le rendu sera alors plus efficace en créant deux scènes, une pour la pièce et l'autre pour les images de la télé. // Cela diminuera le nombre de SceneNode à gérer pour chaque scène, et vous permettra même de ne pas afficher la scène // affichée dans la télé si cette dernière n'est pas visible dans la première scène. NzScene scene; // Nous allons commencer par rajouter des modèles à notre scène // Nous choisirons l'éternel Dr. Freak (Qui ne peut plus être animé car les vieilles animations image-clé // ne sont plus supportées par le moteur pour des raisons techniques) NzModel drfreak; // On charge ensuite le modèle depuis un fichier .md2, le moteur va se charger d'essayer de retrouver les matériaux associés if (!drfreak.LoadFromFile("resources/drfreak.md2")) { std::cout << "Failed to load Dr. Freak" << std::endl; std::getchar(); return EXIT_FAILURE; } // On rajoute également une normal-map externe car elle n'est pas précisée dans le format MD2 // On l'alloue dynamiquement pour ne pas avoir de problème avec les ressources, car en effet, si la texture était supprimée // avant que le modèle ne le soit, alors il y aurait un crash lorsque le modèle supprimerait sa référence vers la texture NzTexture* normalMap = new NzTexture; if (normalMap->LoadFromFile("resources/drfreak_bump.tga")) { // On associe ensuite la normal map au matériau du Dr. Freak NzMaterial* material = drfreak.GetMaterial(0); material->SetNormalMap(normalMap); // On va rendre notre texture non-persistante, cela signifie que lorsque son compteur de référence tombera à zéro, // elle sera automatiquement libérée. (Ce qui sera le cas lorsque tous les modèles l'utilisant auront étés libérés) normalMap->SetPersistent(false); } else { delete normalMap; std::cout << "Failed to load normal map" << std::endl; } // Nous allons faire une centaine de copie du modèle du Dr. Freak, chaque modèle sera indépendant dans ses propriétés // à l'exception de son mesh, de ses matériaux et de son animation s'il en avait eu une, car ceux-ci sont des ressources // lourdes en mémoire (Contrairement au modèle) qui ne seront pas dupliqués mais référencés // Autrement dit, chaque modèle possède une référence vers le mesh et les matériaux du Dr. Freak original, si nous venions // à supprimer le mesh original, il n'y aurait aucun problème (Car les ressources sont toujours utilisées par les autres) std::vector models(100, drfreak); for (unsigned int i = 0; i < models.size(); ++i) { models[i].SetPosition(i/10 * 40, 0.f, i%10 * 40); // On les espace models[i].SetParent(scene); // Et on les attache à la scène } // Nous avons besoin également d'une caméra, pour des raisons évidentes, celle-ci sera placée au dessus des modèles // Et dans leur direction (Nous nous arrangerons également pour en faire une caméra free-fly via les évènements) // On conserve la rotation à part via des angles d'eulers pour la caméra free-fly NzEulerAnglesf camAngles(-45.f, 180.f, 0.f); NzCamera camera; camera.SetPosition(200.f, 50.f, 200); // On place la caméra au milieu de tous les modèles camera.SetRotation(camAngles); camera.SetParent(scene); // On l'attache également à la scène // Et on n'oublie pas de définir les plans délimitant le champs de vision // (Seul ce qui se trouvera entre les deux plans sera rendu) camera.SetZFar(500.f); // La distance entre l'oeil et le plan éloigné camera.SetZNear(1.f); // La distance entre l'oeil et le plan rapproché // Il ne nous manque plus maintenant que de l'éclairage, sans quoi la scène sera complètement noire NzLight spotLight(nzLightType_Spot); // On attache la lumière à la caméra pour qu'elle suive sa position et son orientation, ce qui va aussi l'attacher à la scène // car la caméra y est attachée spotLight.SetParent(camera); // Nous allons maintenant créer la fenêtre, dans laquelle nous ferons nos rendus // Celle-ci demande des paramètres un peu plus complexes // Pour commencer le mode vidéo, celui-ci va définir la taille de la zone de rendu et le nombre de bits par pixels NzVideoMode mode = NzVideoMode::GetDesktopMode(); // Nous récupérons le mode vidéo du bureau // Nous allons prendre les trois quarts de la résolution du bureau pour notre fenêtre mode.width *= 3.f/4.f; mode.height *= 3.f/4.f; // Maintenant le titre, rien de plus simple... NzString windowTitle = "Nazara Demo - First scene"; NzRenderWindow window(mode, windowTitle); if (!window.IsValid()) { std::cout << "Failed to create render window" << std::endl; std::getchar(); return EXIT_FAILURE; } // On fait disparaître le curseur de la souris window.SetCursor(nzWindowCursor_None); // On lie la caméra à la fenêtre camera.SetTarget(window); // Et on créé deux horloges pour gérer le temps NzClock secondClock, updateClock; // Ainsi qu'un compteur de FPS improvisé unsigned int fps = 0; // Début de la boucle de rendu du programme while (window.IsOpen()) { // Ensuite nous allons traiter les évènements (Étape indispensable pour la fenêtre) NzEvent event; while (window.PollEvent(&event)) { switch (event.type) { case nzEventType_MouseMoved: // La souris a bougé { // Gestion de la caméra free-fly (Rotation) float sensitivity = 0.8f; // Sensibilité de la souris // On modifie l'angle de la caméra grâce au déplacement relatif sur X de la souris camAngles.yaw = NzNormalizeAngle(camAngles.yaw - event.mouseMove.deltaX*sensitivity); // Idem, mais pour éviter les problèmes de calcul de la matrice de vue, on restreint les angles camAngles.pitch = NzClamp(camAngles.pitch - event.mouseMove.deltaY*sensitivity, -89.f, 89.f); // On applique les angles d'Euler à notre caméra camera.SetRotation(camAngles); // Pour éviter que le curseur ne sorte de l'écran, nous le renvoyons au centre de la fenêtre // Cette fonction est codée de sorte à ne pas provoquer d'évènement MouseMoved NzMouse::SetPosition(window.GetWidth()/2, window.GetHeight()/2, window); break; } case nzEventType_Quit: // L'utilisateur a cliqué sur la croix, ou l'OS veut terminer notre programme window.Close(); // On demande la fermeture de la fenêtre (Qui aura lieu au prochain tour de boucle) break; case nzEventType_KeyPressed: // Une touche a été pressée ! if (event.key.code == NzKeyboard::Key::Escape) window.Close(); break; default: break; } } // Mise à jour (Caméra) if (updateClock.GetMilliseconds() >= 1000/60) // 60 fois par seconde { // Gestion de la caméra free-fly (Déplacement) float cameraSpeed = 300.f; // Unités par seconde float elapsedTime = updateClock.GetSeconds(); // Si la touche espace est enfoncée, notre vitesse de déplacement est multipliée par deux if (NzKeyboard::IsKeyPressed(NzKeyboard::Space)) cameraSpeed *= 2.f; // Move agit par défaut dans l'espace local de la caméra, autrement dit la rotation est prise en compte // Si la flèche du haut ou la touche Z (vive ZQSD) est pressée, on avance if (NzKeyboard::IsKeyPressed(NzKeyboard::Up) || NzKeyboard::IsKeyPressed(NzKeyboard::Z)) camera.Move(NzVector3f::Forward() * cameraSpeed * elapsedTime); // Si la flèche du bas ou la touche S est pressée, on recule if (NzKeyboard::IsKeyPressed(NzKeyboard::Down) || NzKeyboard::IsKeyPressed(NzKeyboard::S)) camera.Move(NzVector3f::Backward() * cameraSpeed * elapsedTime); // Etc... if (NzKeyboard::IsKeyPressed(NzKeyboard::Left) || NzKeyboard::IsKeyPressed(NzKeyboard::Q)) camera.Move(NzVector3f::Left() * cameraSpeed * elapsedTime); // Etc... if (NzKeyboard::IsKeyPressed(NzKeyboard::Right) || NzKeyboard::IsKeyPressed(NzKeyboard::D)) camera.Move(NzVector3f::Right() * cameraSpeed * elapsedTime); // Majuscule pour monter, mais dans l'espace global (Sans tenir compte de la rotation) if (NzKeyboard::IsKeyPressed(NzKeyboard::LShift) || NzKeyboard::IsKeyPressed(NzKeyboard::RShift)) camera.Move(NzVector3f::Up() * cameraSpeed * elapsedTime, nzCoordSys_Global); // Contrôle (Gauche ou droite) pour descendre dans l'espace global, etc... if (NzKeyboard::IsKeyPressed(NzKeyboard::LControl) || NzKeyboard::IsKeyPressed(NzKeyboard::RControl)) camera.Move(NzVector3f::Down() * cameraSpeed * elapsedTime, nzCoordSys_Global); // On relance l'horloge updateClock.Restart(); } // Rendu de la scène // On active la caméra (Qui s'occupera de préparer la fenêtre au rendu) camera.Activate(); // On procède maintenant au rendu de la scène en elle-même, celui-ci se décompose en quatre étapes distinctes // Pour commencer, on mets à jour la scène, ceci appelle la méthode Update de tous les SceneNode enregistrés // pour la mise à jour globale (Scene::RegisterForUpdate) scene.Update(); // Ensuite il y a le calcul de visibilité, la scène se sert de la caméra active pour effectuer un test de visibilité // afin de faire une liste des SceneNode visibles (Ex: Frustum culling) scene.Cull(); // Ensuite il y a la mise à jour des SceneNode enregistrés pour la mise à jour visible (Exemple: Terrain) scene.UpdateVisible(); // Pour terminer, il y a l'affichage en lui-même, de façon organisée et optimisée (Batching) scene.Draw(); // Après avoir dessiné sur la fenêtre, il faut s'assurer qu'elle affiche cela // Cet appel ne fait rien d'autre qu'échanger les buffers de rendu (Double Buffering) window.Display(); // On incrémente le compteur de FPS improvisé fps++; if (secondClock.GetMilliseconds() >= 1000) // Toutes les secondes { // On compte le nombre de Dr. Freak qui sont affichés actuellement unsigned int visibleNode = 0; for (NzModel& model : models) { if (model.IsVisible()) visibleNode++; } // Et on insère ces données dans le titre de la fenêtre window.SetTitle(windowTitle + " - " + NzString::Number(fps) + " FPS - " + NzString::Number(visibleNode) + " modèles visibles"); /* Note: En C++11 il est possible d'insérer de l'Unicode de façon standard, quel que soit l'encodage du fichier, via quelque chose de similaire à u8"Cha\u00CEne de caract\u00E8res" Cependant, si le code source est encodé en UTF-8 (Comme c'est le cas ici), cela fonctionnera aussi comme ceci : "Chaîne de caractères" */ // Et on réinitialise le compteur de FPS fps = 0; // Et on relance l'horloge pour refaire ça dans une seconde secondClock.Restart(); } } return EXIT_SUCCESS; }