From 16a6a78299ba08522b51866b9c876b27a601ca74 Mon Sep 17 00:00:00 2001 From: Lynix Date: Wed, 6 Mar 2013 01:01:22 +0100 Subject: [PATCH] Added new scene demo Removed bugged demo AnimatedMesh Former-commit-id: 312e27e65ebff620e03733b317c52b3857d1faf9 --- .gitignore | 2 +- examples/AnimatedMesh/main.cpp | 690 ------------------ .../{AnimatedMesh => FirstScene}/build.lua | 4 + examples/FirstScene/main.cpp | 276 +++++++ 4 files changed, 281 insertions(+), 691 deletions(-) delete mode 100644 examples/AnimatedMesh/main.cpp rename examples/{AnimatedMesh => FirstScene}/build.lua (90%) create mode 100644 examples/FirstScene/main.cpp diff --git a/.gitignore b/.gitignore index 17a094d29..9707fcdfe 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ # Nazara build -examples/bin +examples/bin/*.exe lib/libNazara*.a lib/Nazara*.dll lib/Nazara*.so diff --git a/examples/AnimatedMesh/main.cpp b/examples/AnimatedMesh/main.cpp deleted file mode 100644 index f55b2808b..000000000 --- a/examples/AnimatedMesh/main.cpp +++ /dev/null @@ -1,690 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -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 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(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(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()); - } -} diff --git a/examples/AnimatedMesh/build.lua b/examples/FirstScene/build.lua similarity index 90% rename from examples/AnimatedMesh/build.lua rename to examples/FirstScene/build.lua index 18ee3ebd4..5ad2d7c4d 100644 --- a/examples/AnimatedMesh/build.lua +++ b/examples/FirstScene/build.lua @@ -17,24 +17,28 @@ if (_OPTIONS["united"]) then else configuration "DebugStatic" links "Nazara3D-s-d" + links "Nazara2D-s-d" links "NazaraRenderer-s-d" links "NazaraUtility-s-d" links "NazaraCore-s-d" configuration "ReleaseStatic" links "Nazara3D-s" + links "Nazara2D-s" links "NazaraRenderer-s" links "NazaraUtility-s" links "NazaraCore-s" configuration "DebugDLL" links "Nazara3D-d" + links "Nazara2D-d" links "NazaraRenderer-d" links "NazaraUtility-d" links "NazaraCore-d" configuration "ReleaseDLL" links "Nazara3D" + links "Nazara2D" links "NazaraRenderer" links "NazaraUtility" links "NazaraCore" diff --git a/examples/FirstScene/main.cpp b/examples/FirstScene/main.cpp new file mode 100644 index 000000000..240643932 --- /dev/null +++ b/examples/FirstScene/main.cpp @@ -0,0 +1,276 @@ +#include // Module 3D +#include // Horloges +#include // Module de rendu +#include // Module utilitaire +#include + +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 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 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; +}