Added new scene demo
Removed bugged demo AnimatedMesh Former-commit-id: 312e27e65ebff620e03733b317c52b3857d1faf9
This commit is contained in:
parent
619dd3e894
commit
16a6a78299
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
# Nazara build
|
# Nazara build
|
||||||
|
|
||||||
examples/bin
|
examples/bin/*.exe
|
||||||
lib/libNazara*.a
|
lib/libNazara*.a
|
||||||
lib/Nazara*.dll
|
lib/Nazara*.dll
|
||||||
lib/Nazara*.so
|
lib/Nazara*.so
|
||||||
|
|
|
||||||
|
|
@ -1,690 +0,0 @@
|
||||||
#include <Nazara/3D.hpp>
|
|
||||||
#include <Nazara/Core/Clock.hpp>
|
|
||||||
#include <Nazara/Math.hpp>
|
|
||||||
#include <Nazara/Renderer.hpp>
|
|
||||||
#include <Nazara/Utility.hpp>
|
|
||||||
#include <iostream>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
bool CreateCheckerMaterial(NzMaterial* material);
|
|
||||||
bool CreateFloorModel(NzModel* model);
|
|
||||||
void DrawModel(const NzModel& model);
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
// Tout d'abord on affiche les instructions
|
|
||||||
std::cout << "Controls: ZQSD" << std::endl;
|
|
||||||
std::cout << "Escape to quit" << std::endl;
|
|
||||||
std::cout << "Left click to capture/free the mouse" << std::endl;
|
|
||||||
std::cout << "Right click to control Dr. Freak" << std::endl;
|
|
||||||
|
|
||||||
// Maintenant nous initialisons le Renderer (Qui initialisera le noyau ainsi que le module utilitaire)
|
|
||||||
// Cette étape est obligatoire pour beaucoup de fonctionnalités (Notamment le chargement de ressources et le rendu)
|
|
||||||
NzInitializer<Nz3D> nazara;
|
|
||||||
if (!nazara)
|
|
||||||
{
|
|
||||||
// Ça n'a pas fonctionné, le pourquoi se trouve dans le fichier NazaraLog.log
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
NzDebugDrawer::Initialize();
|
|
||||||
|
|
||||||
// Maintenant nous pouvons utiliser le moteur comme bon nous semble, tout d'abord nous allons charger les ressources
|
|
||||||
|
|
||||||
// Charger une ressource se fait actuellement manuellement, mais un ResourceManager est à venir
|
|
||||||
// Vous initialisez une ressource, et la chargez via sa méthode LoadFrom[File|Memory|Stream]
|
|
||||||
// Note: il est possible de donner des instructions au loader (qui va charger le fichier en ressource) via les ResourceParameters
|
|
||||||
NzMeshParams parameters;
|
|
||||||
|
|
||||||
// Le loader doit-il automatiquement charger les animations ?
|
|
||||||
// Attention, ce paramètre possède une signification différente selon le type d'animation du mesh.
|
|
||||||
// -Pour les animations keyframe (image-clé), c'est la seule et unique façon de charger les animations, étant donné
|
|
||||||
// qu'elles sont fourniees avec le mesh.
|
|
||||||
// -Pour les animations squelettiques, le loader ne fera que charger automatiquement l'animation associée au mesh s'il le peut
|
|
||||||
// Dans les deux cas, les paramètres d'animations (parameters.animation) seront utilisés
|
|
||||||
parameters.animated = true; // Vaut true par défaut
|
|
||||||
|
|
||||||
// Pour qu'un mesh puisse être rendu, il doit être stocké du côté de la carte graphique (Hardware), mais il est parfois utile de
|
|
||||||
// le stocker côté RAM, par exemple pour le moteur physique. En sachant qu'il est facile de changer le stockage d'un buffer.
|
|
||||||
parameters.storage = nzBufferStorage_Hardware; // Vaut nzBufferStorage_Hardware par défaut si possible et nzBufferStorage_Software autrement.
|
|
||||||
|
|
||||||
NzModel drfreak;
|
|
||||||
if (!drfreak.LoadFromFile("resources/drfreak.md2")) // On charge notre bon vieux docteur avec les paramètres de chargement.
|
|
||||||
{
|
|
||||||
// Le chargement n'a pas fonctionné, le modèle est peut-être corrompu/non-supporté, ou alors n'existe pas.
|
|
||||||
std::cout << "Failed to load Dr. Freak" << std::endl;
|
|
||||||
std::getchar(); // On laise le temps de voir l'erreur
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!drfreak.HasAnimation()) // Le mesh possède-t-il des animations ?
|
|
||||||
{
|
|
||||||
// Cette démo n'a aucun intérêt sans animations
|
|
||||||
std::cout << "Mesh has no animation" << std::endl;
|
|
||||||
std::getchar();
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nous créons maintenant notre sol
|
|
||||||
NzModel floor;
|
|
||||||
if (!CreateFloorModel(&floor))
|
|
||||||
{
|
|
||||||
std::cout << "Failed to create floor" << std::endl;
|
|
||||||
std::getchar();
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pour effectuer un rendu, il faut que la carte graphique sache comment le faire.
|
|
||||||
// Les shaders sont de petits programmes qui donnent des instructions à la carte graphique lors de son pipeline.
|
|
||||||
// Ils sont aujourd'hui indispensables pour un rendu 3D, mais sont très utiles pour divers effets !
|
|
||||||
// Il existe plusieurs langages de shaders, GLSL pour OpenGL, HLSL pour Direct3D et Cg qui peut être utilisé pour les deux.
|
|
||||||
// Le Renderer de Nazara utilise OpenGL, par conséquent nous utiliserons le GLSL
|
|
||||||
// La méthode NzShader::IsLanguageSupported permet de savoir si un langage est supporté.
|
|
||||||
const NzShader* shader = NzShaderBuilder::Get(nzShaderBuilder_DiffuseMapping);
|
|
||||||
|
|
||||||
// Nos ressources sont chargées, et c'est bien beau, mais il nous faudrait une fenêtre pour afficher tout ça
|
|
||||||
// Window représente une fenêtre singulière, pour y effectuer un rendu il nous faut une RenderWindow
|
|
||||||
// Tout d'abord, sa taille, disons celle du bureau divisé par deux
|
|
||||||
|
|
||||||
// Un VideoMode est une structure contenant une longueur (width), une largeur (height) et le nombre de bits par pixels (bitsPerPixel)
|
|
||||||
NzVideoMode mode = NzVideoMode::GetDesktopMode(); // Nous récupérons le mode actuellement utilisé par le 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;
|
|
||||||
mode.width = 1280;
|
|
||||||
mode.height = 720;
|
|
||||||
// 720p power !
|
|
||||||
|
|
||||||
// Maintenant le titre, rien de plus simple...
|
|
||||||
NzString windowTitle = "Nazara Demo - Skeletal mesh test";
|
|
||||||
|
|
||||||
// Nous pouvons créer notre fenêtre ! (Via le constructeur directement ou par la méthode Create)
|
|
||||||
NzRenderWindow window;
|
|
||||||
|
|
||||||
// Le premier argument définit la taille de rendu de la fenêtre (Si elle possède une bordure elle sera légèrement plus grande).
|
|
||||||
// Le deuxième argument est le titre de la fenêtre lors de sa création, vous pouvez le modifier à tout moment via window.SetTitle.
|
|
||||||
// Le troisième argument représente la décoration de la fenêtre, sa bordure, ses boutons.
|
|
||||||
// Attention que cela permet à la fenêtre de changer sa taille et qu'il faudra donc traiter l'évènement.
|
|
||||||
// Par défaut le troisième argument vaut nzWindowStyle_Default (Bordure + Bouton de fermeture + Redimensionnement)
|
|
||||||
if (!window.Create(mode, windowTitle, nzWindowStyle_Default, NzContextParameters(4)))
|
|
||||||
{
|
|
||||||
std::cout << "Failed to create window" << std::endl;
|
|
||||||
std::getchar();
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notre belle fenêtre est créée, nous pouvons la configurer
|
|
||||||
|
|
||||||
// On cache le curseur
|
|
||||||
window.SetCursor(nzWindowCursor_None);
|
|
||||||
|
|
||||||
// Nous limitons les FPS à 100
|
|
||||||
//window.SetFramerateLimit(100);
|
|
||||||
|
|
||||||
// La matrice de projection définit la transformation du vertice 3D à un point 2D
|
|
||||||
NzRenderer::SetMatrix(nzMatrixType_Projection, NzMatrix4f::Perspective(NzDegrees(70.f), static_cast<float>(window.GetWidth())/window.GetHeight(), 1.f, 10000.f));
|
|
||||||
|
|
||||||
// Notre fenêtre est créée, cependant il faut s'occuper d'elle, pour le rendu et les évènements
|
|
||||||
unsigned int fps = 0; // Compteur de FPS
|
|
||||||
|
|
||||||
// Quelques variables pour notre improvisation de physique
|
|
||||||
NzEulerAnglesf modelRot(0.f, 0.f, 0.f);
|
|
||||||
float modelSpeed = 250.f;
|
|
||||||
|
|
||||||
// Notre caméra
|
|
||||||
NzEulerAnglesf camRot(0.f, 180.f, 0.f); // Les angles d'eulers sont bien plus facile à utiliser
|
|
||||||
|
|
||||||
NzNode camera;
|
|
||||||
camera.SetTranslation(0.f, 50.f, -50.f);
|
|
||||||
camera.SetRotation(camRot);
|
|
||||||
|
|
||||||
NzVector3f camSpeed(100.f);
|
|
||||||
float sensitivity = 0.8f;
|
|
||||||
|
|
||||||
// Quelques variables
|
|
||||||
bool camMode = true;
|
|
||||||
bool paused = false;
|
|
||||||
bool thirdPerson = false;
|
|
||||||
bool windowOpen = true;
|
|
||||||
|
|
||||||
NzClock loadClock;
|
|
||||||
|
|
||||||
NzModel hellknight;
|
|
||||||
//if (!LoadModel("resources/mm/snoutx10k.md5mesh", params, &hellknight))
|
|
||||||
//if (!LoadModel("resources/Boblamp/boblampclean.md5mesh", params, &hellknight))
|
|
||||||
if (!hellknight.LoadFromFile("resources/hellknight.md5mesh"))
|
|
||||||
{
|
|
||||||
std::cout << "Failed to load mesh" << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
NzAnimation* hellknightAnimation = new NzAnimation;
|
|
||||||
//if (!hellknightAnimation.LoadFromFile("resources/mm/idle.md5anim"))
|
|
||||||
//if (!hellknightAnimation.LoadFromFile("resources/Boblamp/boblampclean.md5anim"))0
|
|
||||||
if (!hellknightAnimation->LoadFromFile("resources/hellknight/walk7.md5anim"))
|
|
||||||
{
|
|
||||||
std::cout << "Failed to load animation" << std::endl;
|
|
||||||
delete hellknightAnimation;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hellknightAnimation->SetPersistent(false, false);
|
|
||||||
hellknight.SetAnimation(hellknightAnimation);
|
|
||||||
|
|
||||||
std::cout << "Loaded in " << loadClock.GetSeconds() << std::endl;
|
|
||||||
|
|
||||||
bool drawAabb = false;
|
|
||||||
bool drawSkeleton = false;
|
|
||||||
bool drawHellknight = true;
|
|
||||||
bool drawWireframe = false;
|
|
||||||
|
|
||||||
NzClock secondClock, updateClock; // Des horloges pour gérer le temps
|
|
||||||
|
|
||||||
// On peut commencer la boucle du programme
|
|
||||||
while (windowOpen)
|
|
||||||
{
|
|
||||||
// Ici nous gérons les évènements
|
|
||||||
NzEvent event;
|
|
||||||
while (window.PollEvent(&event)) // Avons-nous un évènement dans la file ?
|
|
||||||
{
|
|
||||||
// Nous avons un évènement !
|
|
||||||
|
|
||||||
switch (event.type) // De quel type est cet évènement ?
|
|
||||||
{
|
|
||||||
case nzEventType_Quit: // L'utilisateur/L'OS nous a demandé de terminer notre exécution
|
|
||||||
windowOpen = false; // Nous terminons alors la boucle
|
|
||||||
break;
|
|
||||||
|
|
||||||
case nzEventType_MouseMoved: // La souris a bougé
|
|
||||||
{
|
|
||||||
// Si nous ne sommes pas en mode free-fly, on ne traite pas l'évènement
|
|
||||||
if (!camMode || thirdPerson)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// On modifie l'angle de la caméra grâce au déplacement relatif de la souris
|
|
||||||
camRot.yaw = NzNormalizeAngle(camRot.yaw - event.mouseMove.deltaX*sensitivity);
|
|
||||||
|
|
||||||
// Pour éviter les loopings mais surtout les problèmes de calculation de la matrice de vue, on restreint les angles
|
|
||||||
camRot.pitch = NzClamp(camRot.pitch - event.mouseMove.deltaY*sensitivity, -89.f, 89.f);
|
|
||||||
|
|
||||||
// La matrice vue représente les transformations effectuées par la caméra
|
|
||||||
// On recalcule la matrice de la caméra et on l'envoie au renderer
|
|
||||||
camera.SetRotation(camRot); // Conversion des angles d'euler en quaternion
|
|
||||||
|
|
||||||
// Pour éviter que le curseur ne sorte de l'écran, nous le renvoyons au centre de la fenêtre
|
|
||||||
NzMouse::SetPosition(window.GetWidth()/2, window.GetHeight()/2, window);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case nzEventType_MouseButtonPressed: // L'utilisateur (ou son chat) vient de cliquer sur la souris
|
|
||||||
if (event.mouseButton.button == NzMouse::Left) // Est-ce le clic gauche ?
|
|
||||||
{
|
|
||||||
// L'utilisateur vient d'appuyer sur le bouton left de la souris
|
|
||||||
// Nous allons inverser le mode caméra et montrer/cacher le curseur en conséquence
|
|
||||||
if (camMode)
|
|
||||||
{
|
|
||||||
camMode = false;
|
|
||||||
window.SetCursor(nzWindowCursor_Default);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
camMode = true;
|
|
||||||
window.SetCursor(nzWindowCursor_None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (event.mouseButton.button == NzMouse::Right) // Est-ce le clic droit ?
|
|
||||||
{
|
|
||||||
if (thirdPerson)
|
|
||||||
{
|
|
||||||
// On arrête le mouvement
|
|
||||||
drfreak.SetSequence("stand");
|
|
||||||
|
|
||||||
// Afin de synchroniser le quaternion avec les angles d'euler
|
|
||||||
NzQuaternionf globalRot = camera.GetDerivedRotation();
|
|
||||||
NzVector3f globalPos = camera.GetDerivedTranslation();
|
|
||||||
camRot = globalRot.ToEulerAngles();
|
|
||||||
|
|
||||||
camera.SetParent(); // On détache la caméra du docteur
|
|
||||||
camera.SetRotation(globalRot);
|
|
||||||
camera.SetTranslation(globalPos);
|
|
||||||
|
|
||||||
thirdPerson = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
camera.SetParent(drfreak); // On accroche la caméra au Dr. Freak
|
|
||||||
camera.SetRotation(NzEulerAnglesf(-35.f, 0.f, 0.f)); // Une rotation pour regarder vers le bas
|
|
||||||
camera.SetTranslation(NzVector3f(0.f, 30.f + drfreak.GetTranslation().y, 50.f));
|
|
||||||
thirdPerson = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case nzEventType_Resized: // L'utilisateur a changé la taille de la fenêtre, le coquin !
|
|
||||||
NzRenderer::SetViewport(NzRectui(0, 0, event.size.width, event.size.height)); // Adaptons l'affichage
|
|
||||||
|
|
||||||
// Il nous faut aussi mettre à jour notre matrice de projection
|
|
||||||
NzRenderer::SetMatrix(nzMatrixType_Projection, NzMatrix4f::Perspective(NzDegrees(70.f), static_cast<float>(event.size.width)/event.size.height, 1.f, 10000.f));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case nzEventType_KeyPressed: // Une touche du clavier vient d'être enfoncée
|
|
||||||
{
|
|
||||||
switch (event.key.code)
|
|
||||||
{
|
|
||||||
case NzKeyboard::Z:
|
|
||||||
case NzKeyboard::S:
|
|
||||||
case NzKeyboard::Q:
|
|
||||||
case NzKeyboard::D:
|
|
||||||
if (thirdPerson)
|
|
||||||
drfreak.SetSequence("run");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NzKeyboard::Escape:
|
|
||||||
windowOpen = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NzKeyboard::P:
|
|
||||||
paused = !paused;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NzKeyboard::F1:
|
|
||||||
if (drawWireframe)
|
|
||||||
{
|
|
||||||
drawWireframe = false;
|
|
||||||
NzRenderer::SetFaceFilling(nzFaceFilling_Fill);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
drawWireframe = true;
|
|
||||||
NzRenderer::SetFaceFilling(nzFaceFilling_Line);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NzKeyboard::F2:
|
|
||||||
drawAabb = !drawAabb;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NzKeyboard::F3:
|
|
||||||
drawSkeleton = !drawSkeleton;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NzKeyboard::F4:
|
|
||||||
drawHellknight = !drawHellknight;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NzKeyboard::F5:
|
|
||||||
{
|
|
||||||
NzTextureSampler::SetDefaultFilterMode(nzSamplerFilter_Trilinear);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NzKeyboard::F6:
|
|
||||||
{
|
|
||||||
NzTextureSampler::SetDefaultFilterMode(nzSamplerFilter_Nearest);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*case NzKeyboard::F5:
|
|
||||||
{
|
|
||||||
NzString animationName;
|
|
||||||
std::cin >> animationName;
|
|
||||||
if (!hellknightAnimation.LoadFromFile("resources/mm/" + animationName + ".md5anim"))
|
|
||||||
{
|
|
||||||
std::cout << "Failed to load animation" << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetSequence(hellknight, 0);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case nzEventType_KeyReleased: // Une touche du clavier vient d'être relachée
|
|
||||||
if (thirdPerson &&
|
|
||||||
!NzKeyboard::IsKeyPressed(NzKeyboard::Z) && // Est-ce que la touche Z est enfoncée en ce moment ?
|
|
||||||
!NzKeyboard::IsKeyPressed(NzKeyboard::S) && // Ou bien la touche S ?
|
|
||||||
!NzKeyboard::IsKeyPressed(NzKeyboard::Q) && // Etc..
|
|
||||||
!NzKeyboard::IsKeyPressed(NzKeyboard::D)) // Etc..
|
|
||||||
{
|
|
||||||
// Si plus aucune touche de déplacement n'est enfoncée
|
|
||||||
drfreak.SetSequence("stand");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // Les autres évènements, on s'en fiche
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mise à jour de la partie logique
|
|
||||||
if (updateClock.GetMilliseconds() >= 1000/60) // 60 fois par seconde
|
|
||||||
{
|
|
||||||
float elapsedTime = updateClock.GetSeconds(); // Le temps depuis la dernière mise à jour
|
|
||||||
|
|
||||||
// Déplacement de la caméra
|
|
||||||
static const NzVector3f forward(NzVector3f::Forward());
|
|
||||||
static const NzVector3f left(NzVector3f::Left());
|
|
||||||
static const NzVector3f up(NzVector3f::Up());
|
|
||||||
|
|
||||||
// Notre rotation sous forme de quaternion nous permet de tourner un vecteur
|
|
||||||
// Par exemple ici, quaternion * forward nous permet de récupérer le vecteur de la direction "avant"
|
|
||||||
|
|
||||||
if (thirdPerson)
|
|
||||||
{
|
|
||||||
// Nous déplaçons le personnage en fonction des touches pressées
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::Z))
|
|
||||||
drfreak.Translate(forward * modelSpeed * elapsedTime);
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::S))
|
|
||||||
drfreak.Translate(-forward * modelSpeed * elapsedTime);
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::Q))
|
|
||||||
drfreak.Rotate(NzEulerAnglesf(0.f, modelSpeed * elapsedTime, 0.f));
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::D))
|
|
||||||
drfreak.Rotate(NzEulerAnglesf(0.f, -modelSpeed * elapsedTime, 0.f));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Sinon, c'est la caméra qui se déplace (en fonction des mêmes touches)
|
|
||||||
|
|
||||||
// Un boost en maintenant le shift gauche
|
|
||||||
NzVector3f speed = (NzKeyboard::IsKeyPressed(NzKeyboard::Key::LShift)) ? camSpeed*5 : camSpeed;
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::Z))
|
|
||||||
camera.Translate(forward * speed * elapsedTime);
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::S))
|
|
||||||
camera.Translate(-forward * speed * elapsedTime);
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::Q))
|
|
||||||
camera.Translate(left * speed * elapsedTime);
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::D))
|
|
||||||
camera.Translate(-left * speed * elapsedTime);
|
|
||||||
|
|
||||||
// En revanche, ici la hauteur est toujours la même, peu importe notre orientation
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::Space))
|
|
||||||
camera.Translate(up * speed * elapsedTime, nzCoordSys_Global);
|
|
||||||
|
|
||||||
if (NzKeyboard::IsKeyPressed(NzKeyboard::LControl))
|
|
||||||
camera.Translate(up * speed * elapsedTime, nzCoordSys_Global);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation
|
|
||||||
if (!paused)
|
|
||||||
{
|
|
||||||
drfreak.Update(elapsedTime);
|
|
||||||
hellknight.Update(elapsedTime);
|
|
||||||
|
|
||||||
/*AnimateModel(hellknight, elapsedTime);
|
|
||||||
hellknight.mesh.GetSkeleton()->GetJoint("luparm")->SetScale(2.f);
|
|
||||||
hellknight.mesh.Skin(hellknight.mesh.GetSkeleton());*/
|
|
||||||
}
|
|
||||||
|
|
||||||
updateClock.Restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
NzRenderer::SetMatrix(nzMatrixType_View, NzMatrix4f::LookAt(camera.GetDerivedTranslation(), camera.GetDerivedTranslation() + camera.GetDerivedRotation() * NzVector3f::Forward()));
|
|
||||||
|
|
||||||
NzVector3f translation = drfreak.GetTranslation();
|
|
||||||
translation.y = -drfreak.GetMesh()->GetAABB().GetMinimum().y;
|
|
||||||
drfreak.SetTranslation(translation);
|
|
||||||
|
|
||||||
// On active le shader et paramètrons le rendu
|
|
||||||
NzRenderer::SetShader(shader);
|
|
||||||
|
|
||||||
// Notre scène 3D requiert un test de profondeur
|
|
||||||
NzRenderer::Enable(nzRendererParameter_DepthTest, true);
|
|
||||||
|
|
||||||
// Nous voulons avoir un fond bien gris
|
|
||||||
NzRenderer::SetClearColor(128, 128, 128);
|
|
||||||
|
|
||||||
// Et nous effaçons les buffers de couleur et de profondeur
|
|
||||||
NzRenderer::Clear(nzRendererClear_Color | nzRendererClear_Depth);
|
|
||||||
|
|
||||||
// Affichage des meshs
|
|
||||||
DrawModel(floor);
|
|
||||||
|
|
||||||
// On élimine les faces qu'on ne voit pas
|
|
||||||
NzRenderer::Enable(nzRendererParameter_FaceCulling, true);
|
|
||||||
|
|
||||||
DrawModel(drfreak);
|
|
||||||
|
|
||||||
if (drawHellknight)
|
|
||||||
DrawModel(hellknight);
|
|
||||||
else
|
|
||||||
NzRenderer::SetMatrix(nzMatrixType_World, hellknight.GetTransformMatrix());
|
|
||||||
|
|
||||||
if (drawSkeleton)
|
|
||||||
{
|
|
||||||
NzDebugDrawer::SetDepthTest(false);
|
|
||||||
NzDebugDrawer::SetPrimaryColor(NzColor::Blue);
|
|
||||||
NzDebugDrawer::Draw(hellknight.GetSkeleton());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawAabb)
|
|
||||||
{
|
|
||||||
NzDebugDrawer::SetDepthTest(true);
|
|
||||||
NzDebugDrawer::SetPrimaryColor(NzColor::Red);
|
|
||||||
NzDebugDrawer::Draw(hellknight.GetAABB());
|
|
||||||
|
|
||||||
NzAxisAlignedBox aabb(drfreak.GetMesh()->GetAABB());
|
|
||||||
aabb.Transform(drfreak.GetTransformMatrix(), false);
|
|
||||||
|
|
||||||
NzRenderer::SetMatrix(nzMatrixType_World, NzMatrix4f::Translate(drfreak.GetDerivedTranslation()));
|
|
||||||
NzDebugDrawer::SetPrimaryColor(NzColor::Red);
|
|
||||||
NzDebugDrawer::Draw(aabb);
|
|
||||||
|
|
||||||
NzRenderer::SetMatrix(nzMatrixType_World, drfreak.GetTransformMatrix());
|
|
||||||
NzDebugDrawer::SetPrimaryColor(NzColor::Blue);
|
|
||||||
NzDebugDrawer::Draw(drfreak.GetMesh()->GetAABB());
|
|
||||||
}
|
|
||||||
|
|
||||||
NzRenderer::Enable(nzRendererParameter_FaceCulling, false);
|
|
||||||
|
|
||||||
// Nous mettons à jour l'écran
|
|
||||||
window.Display();
|
|
||||||
|
|
||||||
fps++;
|
|
||||||
|
|
||||||
// Toutes les secondes
|
|
||||||
if (secondClock.GetMilliseconds() >= 1000)
|
|
||||||
{
|
|
||||||
window.SetTitle(windowTitle + " (FPS: " + NzString::Number(fps) + ')');
|
|
||||||
fps = 0;
|
|
||||||
secondClock.Restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CreateCheckerMaterial(NzMaterial* material)
|
|
||||||
{
|
|
||||||
NzImage image;
|
|
||||||
// Nous crééons une image 2D, au format RGBA8 de dimensions 128*128 (8 blocs de 16 pixels de côté)
|
|
||||||
if (!image.Create(nzImageType_2D, nzPixelFormat_RGBA8, 8*16, 8*16))
|
|
||||||
{
|
|
||||||
// Ne devrait pas arriver (La création d'une image ne peut échouer que si l'un des argument est incorrect)
|
|
||||||
std::cout << "Failed to create image, this means that a bug has been found in Nazara" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pour modifier les pixels, nous pouvons accéder directement à ces derniers avec GetPixels(), ou bien à un pixel
|
|
||||||
// via [Get|Set]PixelColor, mais pour cette occasion nous utiliserons une méthode bien pratique, Fill.
|
|
||||||
unsigned int blockCountX = image.GetWidth()/16;
|
|
||||||
unsigned int blockCountY = image.GetHeight()/16;
|
|
||||||
for (unsigned int x = 0; x < blockCountX; ++x)
|
|
||||||
{
|
|
||||||
for (unsigned int y = 0; y < blockCountY; ++y)
|
|
||||||
{
|
|
||||||
// Une belle texture de damier
|
|
||||||
NzColor color = (x%2 == y%2) ? NzColor::White : NzColor::Black;
|
|
||||||
// Fill remplit une zone de la texture avec une couleur
|
|
||||||
image.Fill(color, NzRectui(x*16, y*16, 16, 16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NzTexture* texture = new NzTexture;
|
|
||||||
if (!texture->LoadFromImage(image)) // Nous créons notre texture depuis notre image
|
|
||||||
{
|
|
||||||
// Nous n'avons vraiment pas beaucoup de chance..
|
|
||||||
std::cout << "Failed to load image" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
material->SetDiffuseMap(texture);
|
|
||||||
|
|
||||||
texture->SetPersistent(false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CreateFloorModel(NzModel* model)
|
|
||||||
{
|
|
||||||
// Cette fonction créé un mesh statique simpliste pour servir de sol
|
|
||||||
NzMesh* mesh = new NzMesh;
|
|
||||||
// Nous créons un mesh statique
|
|
||||||
if (!mesh->CreateStatic())
|
|
||||||
{
|
|
||||||
// L'échec est techniquement impossible mais le moteur étant en constante évolution ...
|
|
||||||
std::cout << "Failed to create mesh" << std::endl;
|
|
||||||
delete mesh;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Les vertex declaration ont pour seul but de décrire l'agencement d'un vertex buffer
|
|
||||||
// Elles sont composées de VertexElement, chacun décrivant un élément du buffer
|
|
||||||
NzVertexDeclaration* declaration = new NzVertexDeclaration;
|
|
||||||
|
|
||||||
// Il y a cinq paramètres différents (stream, usage, type, offset, usageIndex)
|
|
||||||
// -Stream: À quoi serviront les données ? À définir des sommets (nzElementStream_VertexData) ou à l'instancing (nzElementStream_InstancedData)
|
|
||||||
// -Usage: Comment cette donnée doit-elle être envoyée au shader
|
|
||||||
// -Type: Comment sont stockées ces données ? (Un triplet de float ? Deux double ? ..)
|
|
||||||
// -Offset: La position de la donnée dans le buffer (les données sont entrelacées)
|
|
||||||
// -UsageIndex: Pour les coordonnées de texture, définit l'indice de texture utilisé.
|
|
||||||
NzVertexElement elements[2];
|
|
||||||
elements[0].usage = nzElementUsage_Position; // Notre premier élément sera la position des vertices
|
|
||||||
elements[0].offset = 0; // Celles-ci sont placées au début
|
|
||||||
elements[0].type = nzElementType_Float3; // Sont composées de trois flottants
|
|
||||||
|
|
||||||
elements[1].usage = nzElementUsage_TexCoord;
|
|
||||||
elements[1].offset = 3*sizeof(float);
|
|
||||||
elements[1].type = nzElementType_Float2;
|
|
||||||
|
|
||||||
if (!declaration->Create(elements, 2))
|
|
||||||
{
|
|
||||||
// Nos éléments sont invalides !
|
|
||||||
std::cout << "Failed to create vertex declaration" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nous créons ensuite un buffer de 4 vertices (le second argument précise l'espace pris par chaque vertex), le stockage
|
|
||||||
// Et nous indiquons que nous n'y toucherons plus
|
|
||||||
NzVertexBuffer* buffer = new NzVertexBuffer(declaration, 4, nzBufferStorage_Hardware, nzBufferUsage_Static);
|
|
||||||
|
|
||||||
// Doit respecter la declaration
|
|
||||||
float vertices[] =
|
|
||||||
{
|
|
||||||
// Vertex 1
|
|
||||||
-1000.f, 0.f, -1000.f, // Position
|
|
||||||
0.f, 0.f, // UV
|
|
||||||
|
|
||||||
// Vertex 2
|
|
||||||
-1000.f, 0.f, 1000.f, // Position
|
|
||||||
0.f, 10.f, // UV
|
|
||||||
|
|
||||||
// Vertex 3
|
|
||||||
1000.f, 0.f, -1000.f, // Position
|
|
||||||
10.f, 0.f, // UV
|
|
||||||
|
|
||||||
// Vertex 4
|
|
||||||
1000.f, 0.f, 1000.f, // Position
|
|
||||||
10.f, 10.f // UV
|
|
||||||
};
|
|
||||||
|
|
||||||
// Afin de modifier un buffer, il nous faut soit le verrouiller (accès bas-niveau), soit le remplir (accès de plus haut niveau)
|
|
||||||
if (!buffer->Fill(vertices, 0, 4)) // Nous remplissons à partir de l'index 0, et nous envoyons 4 vertices
|
|
||||||
{
|
|
||||||
std::cout << "Failed to fill buffer" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
NzStaticMesh* subMesh = new NzStaticMesh(mesh);
|
|
||||||
if (!subMesh->Create(buffer))
|
|
||||||
{
|
|
||||||
std::cout << "Failed to create subMesh" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
subMesh->SetMaterialIndex(0);
|
|
||||||
subMesh->SetPrimitiveType(nzPrimitiveType_TriangleStrip);
|
|
||||||
|
|
||||||
// On ajoute le submesh au mesh
|
|
||||||
mesh->AddSubMesh(subMesh);
|
|
||||||
mesh->SetMaterialCount(1);
|
|
||||||
|
|
||||||
// Nos ressources sont notifiées utilisées par le mesh et le submesh, nous pouvons les rendre éphèmères.
|
|
||||||
// Les ressources seront donc automatiquement libérées lorsqu'elles ne seront plus référencées par une classe
|
|
||||||
buffer->SetPersistent(false);
|
|
||||||
declaration->SetPersistent(false);
|
|
||||||
subMesh->SetPersistent(false); // Pour le submesh, c'est déjà le comportement par défaut
|
|
||||||
|
|
||||||
NzModelParameters params;
|
|
||||||
params.loadAnimation = false;
|
|
||||||
params.loadMaterials = false;
|
|
||||||
|
|
||||||
model->SetMesh(mesh, params);
|
|
||||||
mesh->SetPersistent(false);
|
|
||||||
|
|
||||||
NzMaterial* material = new NzMaterial;
|
|
||||||
CreateCheckerMaterial(material);
|
|
||||||
|
|
||||||
model->SetMaterial(0, material);
|
|
||||||
material->SetPersistent(false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawModel(const NzModel& model)
|
|
||||||
{
|
|
||||||
// La matrice world est celle qui représente les transformations du modèle
|
|
||||||
NzRenderer::SetMatrix(nzMatrixType_World, model.GetTransformMatrix());
|
|
||||||
|
|
||||||
// Un mesh est divisé en plusieurs submeshes
|
|
||||||
unsigned int subMeshCount = model.GetMesh()->GetSubMeshCount();
|
|
||||||
for (unsigned int i = 0; i < subMeshCount; ++i)
|
|
||||||
{
|
|
||||||
// On récupère le submesh
|
|
||||||
const NzSubMesh* subMesh = model.GetMesh()->GetSubMesh(i);
|
|
||||||
|
|
||||||
model.GetMaterial(i)->Apply();
|
|
||||||
|
|
||||||
NzRenderer::SetVertexBuffer(subMesh->GetVertexBuffer());
|
|
||||||
|
|
||||||
// On fait le rendu
|
|
||||||
const NzIndexBuffer* indexBuffer = subMesh->GetIndexBuffer();
|
|
||||||
if (indexBuffer)
|
|
||||||
{
|
|
||||||
NzRenderer::SetIndexBuffer(indexBuffer);
|
|
||||||
NzRenderer::DrawIndexedPrimitives(subMesh->GetPrimitiveType(), 0, indexBuffer->GetIndexCount());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
NzRenderer::DrawPrimitives(subMesh->GetPrimitiveType(), 0, subMesh->GetVertexCount());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,24 +17,28 @@ if (_OPTIONS["united"]) then
|
||||||
else
|
else
|
||||||
configuration "DebugStatic"
|
configuration "DebugStatic"
|
||||||
links "Nazara3D-s-d"
|
links "Nazara3D-s-d"
|
||||||
|
links "Nazara2D-s-d"
|
||||||
links "NazaraRenderer-s-d"
|
links "NazaraRenderer-s-d"
|
||||||
links "NazaraUtility-s-d"
|
links "NazaraUtility-s-d"
|
||||||
links "NazaraCore-s-d"
|
links "NazaraCore-s-d"
|
||||||
|
|
||||||
configuration "ReleaseStatic"
|
configuration "ReleaseStatic"
|
||||||
links "Nazara3D-s"
|
links "Nazara3D-s"
|
||||||
|
links "Nazara2D-s"
|
||||||
links "NazaraRenderer-s"
|
links "NazaraRenderer-s"
|
||||||
links "NazaraUtility-s"
|
links "NazaraUtility-s"
|
||||||
links "NazaraCore-s"
|
links "NazaraCore-s"
|
||||||
|
|
||||||
configuration "DebugDLL"
|
configuration "DebugDLL"
|
||||||
links "Nazara3D-d"
|
links "Nazara3D-d"
|
||||||
|
links "Nazara2D-d"
|
||||||
links "NazaraRenderer-d"
|
links "NazaraRenderer-d"
|
||||||
links "NazaraUtility-d"
|
links "NazaraUtility-d"
|
||||||
links "NazaraCore-d"
|
links "NazaraCore-d"
|
||||||
|
|
||||||
configuration "ReleaseDLL"
|
configuration "ReleaseDLL"
|
||||||
links "Nazara3D"
|
links "Nazara3D"
|
||||||
|
links "Nazara2D"
|
||||||
links "NazaraRenderer"
|
links "NazaraRenderer"
|
||||||
links "NazaraUtility"
|
links "NazaraUtility"
|
||||||
links "NazaraCore"
|
links "NazaraCore"
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
#include <Nazara/3D.hpp> // Module 3D
|
||||||
|
#include <Nazara/Core/Clock.hpp> // Horloges
|
||||||
|
#include <Nazara/Renderer.hpp> // Module de rendu
|
||||||
|
#include <Nazara/Utility.hpp> // Module utilitaire
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// Pour commencer, nous initialisons le module 3D, celui-ci va provoquer l'initialisation (dans l'ordre),
|
||||||
|
// du noyau (Core), Utility, Renderer, 2D.
|
||||||
|
// 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<Nz3D> 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 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<NzModel> 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é du déplacement
|
||||||
|
|
||||||
|
// On modifie l'angle de la caméra grâce au déplacement relatif de la souris
|
||||||
|
camAngles.yaw = NzNormalizeAngle(camAngles.yaw - event.mouseMove.deltaX*sensitivity);
|
||||||
|
|
||||||
|
// Pour éviter les loopings mais surtout les problèmes de calculation de la matrice de vue, on restreint les angles
|
||||||
|
camAngles.pitch = NzClamp(camAngles.pitch - event.mouseMove.deltaY*sensitivity, -89.f, 89.f);
|
||||||
|
|
||||||
|
// On applique l'angle d'euler à la caméra
|
||||||
|
camera.SetRotation(camAngles);
|
||||||
|
|
||||||
|
// Pour éviter que le curseur ne sorte de l'écran, nous le renvoyons au centre de la fenêtre
|
||||||
|
// La fonction est codée de sorte qu'elle ne provoquera pas 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.Destroy(); // On ferme la fenêtre sans demander notre reste
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nzEventType_KeyPressed: // Une touche a été pressée !
|
||||||
|
if (event.key.code == NzKeyboard::Key::Escape)
|
||||||
|
window.Destroy();
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 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::Forward() * -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::Left() * -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, etc...
|
||||||
|
if (NzKeyboard::IsKeyPressed(NzKeyboard::LControl) || NzKeyboard::IsKeyPressed(NzKeyboard::RControl))
|
||||||
|
camera.Move(NzVector3f::Up() * -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 vide le buffer de couleur et de profondeur pour réinitialiser le rendu
|
||||||
|
// À l'avenir, ceci sera fait automatiquement par la scène via les backgrounds
|
||||||
|
NzRenderer::Clear(nzRendererClear_Color | nzRendererClear_Depth);
|
||||||
|
|
||||||
|
// 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) + u8" mod\u00E8les visibles");
|
||||||
|
/*
|
||||||
|
Note: En C++11 il est possible d'insérer de l'Unicode de façon standard,
|
||||||
|
via quelque chose de similaire à u8"Cha\u00CEne de caract\u00E8res"
|
||||||
|
Cependant, si le code source est encodé en UTF-8, 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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue