From 265e1c0fbd67d316fde6f2c798d2a4c5b0ebed9d Mon Sep 17 00:00:00 2001 From: Gawaboumga Date: Mon, 4 Apr 2016 10:17:05 +0200 Subject: [PATCH 1/2] Fix compilation for gcc/clang + tests for serialization Former-commit-id: db9c93a1db3c57f268fc17e411402e071fc8675a --- SDK/include/NDK/Application.hpp | 1 + SDK/include/NDK/Application.inl | 2 + SDK/include/NDK/BaseComponent.inl | 2 +- SDK/src/NDK/LuaBinding_SDK.cpp | 4 +- SDK/src/NDK/World.cpp | 2 +- examples/Mario/main.cpp | 447 +++++++++++++++++++ include/Nazara/Audio/OpenAL.hpp | 3 +- include/Nazara/Core/ResourceSaver.hpp | 1 + include/Nazara/Core/ResourceSaver.inl | 2 +- include/Nazara/Graphics/SkyboxBackground.hpp | 2 +- include/Nazara/Math/BoundingVolume.inl | 2 +- include/Nazara/Math/Frustum.hpp | 8 +- include/Nazara/Math/Frustum.inl | 12 +- include/Nazara/Network/RUdpConnection.hpp | 8 +- include/Nazara/Renderer/OpenGL.hpp | 3 +- src/Nazara/Audio/OpenAL.cpp | 3 +- src/Nazara/Network/RUdpConnection.cpp | 2 +- src/Nazara/Renderer/OpenGL.cpp | 3 +- tests/Engine/Core/Serialization.cpp | 261 +++++++++++ tests/resources/Engine/Core/FileTest.txt | 1 + 20 files changed, 747 insertions(+), 22 deletions(-) create mode 100644 examples/Mario/main.cpp create mode 100644 tests/Engine/Core/Serialization.cpp create mode 100644 tests/resources/Engine/Core/FileTest.txt diff --git a/SDK/include/NDK/Application.hpp b/SDK/include/NDK/Application.hpp index ef697e48a..5d918ef8e 100644 --- a/SDK/include/NDK/Application.hpp +++ b/SDK/include/NDK/Application.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace Ndk diff --git a/SDK/include/NDK/Application.inl b/SDK/include/NDK/Application.inl index e65d8d5d0..f44ff9e8c 100644 --- a/SDK/include/NDK/Application.inl +++ b/SDK/include/NDK/Application.inl @@ -38,6 +38,7 @@ namespace Ndk s_application = nullptr; } + #ifndef NDK_SERVER template T& Application::AddWindow(Args&&... args) { @@ -46,6 +47,7 @@ namespace Ndk m_windows.emplace_back(new T(std::forward(args)...)); return static_cast(*m_windows.back().get()); } + #endif template World& Application::AddWorld(Args&&... args) diff --git a/SDK/include/NDK/BaseComponent.inl b/SDK/include/NDK/BaseComponent.inl index f6bd79578..71f8ee2c1 100644 --- a/SDK/include/NDK/BaseComponent.inl +++ b/SDK/include/NDK/BaseComponent.inl @@ -2,7 +2,7 @@ // This file is part of the "Nazara Development Kit" // For conditions of distribution and use, see copyright notice in Prerequesites.hpp -#include +#include #include namespace Ndk diff --git a/SDK/src/NDK/LuaBinding_SDK.cpp b/SDK/src/NDK/LuaBinding_SDK.cpp index ec041e4b2..3d5088a7e 100644 --- a/SDK/src/NDK/LuaBinding_SDK.cpp +++ b/SDK/src/NDK/LuaBinding_SDK.cpp @@ -59,6 +59,7 @@ namespace Ndk application.SetMethod("Quit", &Application::Quit); /*********************************** Ndk::Console **********************************/ + #ifndef NDK_SERVER consoleClass.Inherit(nodeClass, [] (ConsoleHandle* handle) -> Nz::Node* { return handle->GetObject(); @@ -84,6 +85,7 @@ namespace Ndk //consoleClass.SetMethod("SetTextFont", &Console::SetTextFont); consoleClass.SetMethod("Show", &Console::Show, true); + #endif /*********************************** Ndk::Entity **********************************/ entityClass.SetMethod("Enable", &Entity::Enable); @@ -248,4 +250,4 @@ namespace Ndk } instance.SetGlobal("ComponentType"); } -} \ No newline at end of file +} diff --git a/SDK/src/NDK/World.cpp b/SDK/src/NDK/World.cpp index 1762f1d12..fc53143dd 100644 --- a/SDK/src/NDK/World.cpp +++ b/SDK/src/NDK/World.cpp @@ -14,7 +14,7 @@ namespace Ndk { - World::~World() + World::~World() noexcept { // La destruction doit se faire dans un ordre précis Clear(); diff --git a/examples/Mario/main.cpp b/examples/Mario/main.cpp new file mode 100644 index 000000000..24fef574f --- /dev/null +++ b/examples/Mario/main.cpp @@ -0,0 +1,447 @@ +/* +** FirstScene - Première scène graphique +** Prérequis: Aucun +** Utilisation du module utilitaire et graphique +** Présente: +** - Création et gestion d'une fenêtre (Traitement des évènements clavier/souris) +** - Gestion du clavier (Récupération de l'état d'une touche) +** - Des outils pour afficher une scène basique via le chargement d'un modèle (et son affichage) +** - Éclairage directionnel +** - Gestion d'une caméra free-fly (Avec déplacement fluide) +** - Gestion basique d'une horloge +*/ + +#include // Horloges +#include // Module graphique +#include // Module de rendu +#include // Module utilitaire +#include +#include +#include +#include +#include +#include +#include +#include + +// Petite fonction permettant de rendre le déplacement de la caméra moins ridige +Nz::Vector3f DampedString(const Nz::Vector3f& currentPos, const Nz::Vector3f& targetPos, float frametime, float springStrength = 3.f); + +int main() +{ + // Pour commencer, nous initialisons le SDK de Nazara, celui-ci va préparer le terrain en initialisant le moteur, + // les composants, systèmes, etc. + // 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. + Nz::Initializer 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 le monde pour contenir notre scène. + // Dans un ECS, le monde représente bien ce que son nom indique, c'est l'ensemble de ce qui existe au niveau de l'application. + // Il contient les systèmes et les entités, ces dernières contiennent les composants. + // Il est possible d'utiliser plusieurs mondes au sein d'une même application, par exemple pour gérer un mélange de 2D et de 3D, + // mais nous verrons cela dans un prochain exemple. + Ndk::World world; + + // Nous pouvons maintenant ajouter des systèmes, mais dans cet exemple nous nous contenterons de ceux de base. + + // La première chose que nous faisons est d'ajouter un background (fond) à notre scène. + // Il en existe plusieurs types, le moteur inclut pour l'instant trois d'entre eux: + // -ColorBackground: Une couleur unie en fond + // -SkyboxBackground: Une skybox en fond, un cube à six faces rendu autour de la caméra (En perdant la notion de distance) + // -TextureBackground: Une texture en fond, celle-ci sera affichée derrière la scène + + // Nous choisirons ici une Skybox, cette dernière étant l'effet le plus réussi et convenant très bien à une scène spatiale + // Pour commencer il faut charger une texture de type cubemap, certaines images sont assemblées de cette façon, + // comme celle que nous allons utiliser. + // En réalité les textures "cubemap" regroupent six faces en une, pour faciliter leur utilisation. + + // Nous créons une nouvelle texture et prenons une référence sur celle-ci (à la manière des pointeurs intelligents) + Nz::TextureRef texture = Nz::Texture::New(); + if (texture->LoadCubemapFromFile("resources/skybox-space.png")) + { + // Si la création du cubemap a fonctionné + + // Nous créons alors le background à partir de notre texture (celui-ci va référencer notre texture, notre pointeur ne sert alors plus à rien). + Nz::SkyboxBackgroundRef skybox = Nz::SkyboxBackground::New(std::move(texture)); + + // Accédons maintenant au système de rendu faisant partie du monde + Ndk::RenderSystem& renderSystem = world.GetSystem(); // Une assertion valide la précondition "le système doit faire partie du monde" + + // Nous assignons ensuite notre skybox comme "fond par défaut" du système + // La notion "par défaut" existe parce qu'une caméra pourrait utiliser son propre fond lors du rendu, + // le fond par défaut est utilisé lorsque la caméra n'a pas de fond propre assigné + renderSystem.SetDefaultBackground(std::move(skybox)); + + // Notre skybox est maintenant référencée par le système, lui-même appartenant au monde, aucune libération explicite n'est nécessaire + } + else + // Le chargement a échoué + std::cout << "Failed to load skybox" << std::endl; + + // Ensuite, nous allons rajouter un modèle à notre scène. + + // Les modèles représentent, globalement, tout ce qui est visible en trois dimensions. + // Nous choisirons ici un vaisseau spatial (Quoi de mieux pour une scène spatiale ?) + + // Encore une fois, nous récupérons une référence plutôt que l'objet lui-même (cela va être très utile par la suite) + Nz::ModelRef spaceshipModel = Nz::Model::New(); + + // Nous allons charger notre modèle depuis un fichier, mais nous pouvons ajuster le modèle lors du chargement via + // une structure permettant de paramétrer le chargement des modèles + Nz::ModelParameters params; + + // Le format OBJ ne précise aucune échelle pour ses données, contrairement à Nazara (une unité = un mètre en 3D). + // Comme le vaisseau est très grand (Des centaines de mètres de long), nous allons le rendre plus petit pour les besoins de la démo. + // Ce paramètre sert à indiquer la mise à l'échelle désirée lors du chargement du modèle. + params.mesh.scale.Set(0.01f); // Un centième de la taille originelle + + // Les UVs de ce fichier sont retournées (repère OpenGL, origine coin bas-gauche) par rapport à ce que le moteur attend (haut-gauche) + // Nous devons donc indiquer au moteur de les retourner lors du chargement + params.mesh.flipUVs = true; + + // Nazara va par défaut optimiser les modèles pour un rendu plus rapide, cela peut prendre du temps et n'est pas nécessaire ici + params.mesh.optimizeIndexBuffers = false; + + // On charge ensuite le modèle depuis son fichier + // Le moteur va charger le fichier et essayer de retrouver les fichiers associés (comme les matériaux, textures, ...) + if (!spaceshipModel->LoadFromFile("resources/Spaceship/spaceship.obj", params)) + { + // Si le chargement a échoué (fichier inexistant/invalide), il ne sert à rien de continuer + std::cout << "Failed to load spaceship" << std::endl; + std::getchar(); + + return EXIT_FAILURE; + } + + // Nous voulons afficher quelques statistiques relatives au modèle, comme le nombre de sommets et de triangles + // Pour cela, nous devons accéder au mesh (maillage 3D) + + // Note: Si nous voulions stocker le mesh pour nous en servir après, nous devrions alors récupérer une référence pour nous assurer + // qu'il ne sera pas supprimé tant que nous l'utilisons, mais ici nous faisons un accès direct et ne nous servons plus du pointeur par la suite + // Il est donc acceptable d'utiliser un pointeur nu ici. + Nz::Mesh* mesh = spaceshipModel->GetMesh(); + std::cout << mesh->GetVertexCount() << " sommets" << std::endl; + std::cout << mesh->GetTriangleCount() << " triangles" << std::endl; + + // En revanche, le format OBJ ne précise pas l'utilisation d'une normal map, nous devons donc la charger manuellement + // Pour commencer on récupère le matériau du mesh, celui-ci en possède plusieurs mais celui qui nous intéresse, + // celui de la coque, est le second (Cela est bien entendu lié au modèle en lui-même) + Nz::Material* material = spaceshipModel->GetMaterial(1); // Encore une fois nous ne faisons qu'un accès direct. + + // On lui indique ensuite le chemin vers la normal map + if (!material->SetNormalMap("resources/Spaceship/Texture/normal.png")) + { + // Le chargement a échoué, peut-être le fichier n'existe pas, ou n'est pas reconnu par le moteur + // Mais ce n'est pas une erreur critique, le rendu peut quand même se faire (Mais sera moins détaillé) + std::cout << "Failed to load normal map" << std::endl; + } + + // Bien, nous avons un modèle valide, mais celui-ci ne consiste qu'en des informations de rendu, de matériaux et de textures. + // Commençons donc par créer une entité vide, cela se fait en demandant au monde de générer une nouvelle entité. + Ndk::EntityHandle spaceship = world.CreateEntity(); + + // Note: Nous ne récupérons pas l'entité directement mais un "handle" vers elle, ce dernier est un pointeur intelligent non-propriétaire. + // Pour des raisons techniques, le pointeur de l'entité peut venir à changer, ou l'entité être simplement détruite pour n'importe quelle raison. + // Le Handle nous permet de maintenir un pointeur valide vers notre entité, et invalidé automatiquement à sa mort. + + // Nous avons désormais une entité, mais celle-ci ne contient rien et n'a d'autre propriété qu'un identifiant + // Nous devons donc lui rajouter les composants que nous voulons. + + // Un NodeComponent donne à notre entité une position, rotation, échelle, et nous permet de l'attacher à d'autres entités (ce que nous ne ferons pas ici). + // Étant donné que par défaut, un NodeComponent se place en (0,0,0) sans rotation et avec une échelle de 1,1,1 et que cela nous convient, + // nous n'avons pas besoin d'agir sur le composant créé. + spaceship->AddComponent(); + + // Bien, notre entité nouvellement créé dispose maintenant d'une position dans la scène, mais est toujours invisible + // Nous lui ajoutons donc un GraphicsComponent + Ndk::GraphicsComponent& spaceshipGraphics = spaceship->AddComponent(); + + // Ce composant sert de point d'attache pour tous les renderables instanciés (tels que les modèles, les sprites, le texte, etc.) + // Cela signifie également qu'un modèle peut être attaché à autant d'entités que nécessaire. + // Note: Afin de maximiser les performances, essayez d'avoir le moins de renderables instanciés/matériaux et autres ressources possible + // le moteur fonctionne selon le batching et regroupera par exemple tous les modèles identiques ensembles lors du rendu. + spaceshipGraphics.Attach(spaceshipModel); + + // Nous avons besoin également d'une caméra pour servir de point de vue à notre scène, celle-ci sera à l'écart du modèle + // regardant dans sa direction. + + // On conserve la rotation à part via des angles d'eulers pour la caméra free-fly + Nz::EulerAnglesf camAngles(0.f, -20.f, 0.f); + + // Nous créons donc une seconde entité + // Note: La création d'entité est une opération légère au sein du moteur, mais plus vous aurez d'entités et plus le processeur devra travailler. + Ndk::EntityHandle camera = world.CreateEntity(); + + // Notre caméra a elle aussi besoin d'être positionnée dans la scène + Ndk::NodeComponent& cameraNode = camera->AddComponent(); + cameraNode.SetPosition(0.f, 0.25f, 2.f); // On place la caméra à l'écart + cameraNode.SetRotation(camAngles); + + // Et dispose d'un composant pour chaque point de vue de la scène, le CameraComponent + Ndk::CameraComponent& cameraComp = camera->AddComponent(); + + // 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) + + // La distance entre l'oeil et le plan éloigné + cameraComp.SetZFar(5000.f); + + // La distance entre l'oeil et le plan rapproché (0 est une valeur interdite car la division par zéro l'est également) + cameraComp.SetZNear(0.1f); + + // Attention que le ratio entre les deux (zFar/zNear) doit rester raisonnable, dans le cas contraire vous risquez un phénomène + // de "Z-Fighting" (Impossibilité de déduire quelle surface devrait apparaître en premier) sur les surfaces éloignées. + + // Il ne nous manque plus maintenant que de l'éclairage, sans quoi la scène sera complètement noire + // Il existe trois types de lumières: + // -DirectionalLight: Lumière infinie sans position, envoyant de la lumière dans une direction particulière + // -PointLight: Lumière située à un endroit précis, envoyant de la lumière finie dans toutes les directions + // -SpotLight: Lumière située à un endroit précis, envoyant de la lumière vers un endroit donné, avec un angle de diffusion + + // Nous allons créer une lumière directionnelle pour représenter la nébuleuse de notre skybox + // Encore une fois, nous créons notre entité + Ndk::EntityHandle nebulaLight = world.CreateEntity(); + + // Lui ajoutons une position dans la scène + Ndk::NodeComponent& nebulaLightNode = nebulaLight->AddComponent(); + + // Et ensuite le composant principal, le LightComponent + Ndk::LightComponent& nebulaLightComp = nebulaLight->AddComponent(Nz::LightType_Directional); + + // Il nous faut ensuite configurer la lumière + // Pour commencer, sa couleur, la nébuleuse étant d'une couleur jaune, j'ai choisi ces valeurs + nebulaLightComp.SetColor(Nz::Color(255, 182, 90)); + + // Nous appliquons ensuite une rotation de sorte que la lumière dans la même direction que la nébuleuse + nebulaLightNode.SetRotation(Nz::EulerAnglesf(0.f, 102.f, 0.f)); + + // Nous allons maintenant créer la fenêtre, dans laquelle nous ferons nos rendus + // Celle-ci demande des paramètres 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 + Nz::VideoMode mode = Nz::VideoMode::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 * mode.width / 4; + mode.height = 3 * mode.height / 4; + + // Maintenant le titre, rien de plus simple... + Nz::String windowTitle = "Nazara Demo - First scene"; + + // Ensuite, le "style" de la fenêtre, possède-t-elle des bordures, peut-on cliquer sur la croix de fermeture, + // peut-on la redimensionner, ... + Nz::WindowStyleFlags style = Nz::WindowStyle_Default; // Nous prenons le style par défaut, autorisant tout ce que je viens de citer + + // Ensuite, les paramètres du contexte de rendu + // On peut configurer le niveau d'antialiasing, le nombre de bits du depth buffer et le nombre de bits du stencil buffer + // Nous désirons avoir un peu d'antialiasing (4x), les valeurs par défaut pour le reste nous conviendrons très bien + Nz::RenderTargetParameters parameters; + parameters.antialiasingLevel = 4; + + Nz::RenderWindow window(mode, windowTitle, style, parameters); + 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(Nz::WindowCursor_None); + + // On lie la caméra à la fenêtre + cameraComp.SetTarget(&window); + + // Et on créé deux horloges pour gérer le temps + Nz::Clock secondClock, updateClock; + Nz::UInt64 updateAccumulator = 0; + + // Ainsi qu'un compteur de FPS improvisé + unsigned int fps = 0; + + // Quelques variables de plus pour notre caméra + bool smoothMovement = true; + Nz::Vector3f targetPos = cameraNode.GetPosition(); + + // 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) + Nz::WindowEvent event; + while (window.PollEvent(&event)) + { + switch (event.type) + { + case Nz::WindowEventType_MouseMoved: // La souris a bougé + { + // Gestion de la caméra free-fly (Rotation) + float sensitivity = 0.3f; // 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 = Nz::NormalizeAngle(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 = Nz::Clamp(camAngles.pitch - event.mouseMove.deltaY*sensitivity, -89.f, 89.f); + + // On applique les angles d'Euler à notre caméra + cameraNode.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 + Nz::Mouse::SetPosition(window.GetWidth()/2, window.GetHeight()/2, window); + break; + } + + case Nz::WindowEventType_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 Nz::WindowEventType_KeyPressed: // Une touche a été pressée ! + if (event.key.code == Nz::Keyboard::Key::Escape) + window.Close(); + else if (event.key.code == Nz::Keyboard::F1) + { + if (smoothMovement) + { + targetPos = cameraNode.GetPosition(); + smoothMovement = false; + } + else + smoothMovement = true; + } + break; + + default: + break; + } + } + + Nz::UInt64 elapsedUS = updateClock.GetMicroseconds(); + // On relance l'horloge + updateClock.Restart(); + + // Mise à jour (Caméra) + const Nz::UInt64 updateRate = 1000000 / 60; // 60 fois par seconde + updateAccumulator += elapsedUS; + + if (updateAccumulator >= updateRate) + { + // Le temps écoulé en seconde depuis la dernière fois que ce bloc a été exécuté + float elapsedTime = updateAccumulator / 1000000.f; + std::cout << elapsedTime << std::endl; + + // Vitesse de déplacement de la caméra + float cameraSpeed = 3.f * elapsedTime; // Trois mètres par seconde + + // Si la touche espace est enfoncée, notre vitesse de déplacement est multipliée par deux + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Space)) + cameraSpeed *= 2.f; + + // Pour que nos déplacement soient liés à la rotation de la caméra, nous allons utiliser + // les directions locales de la caméra + + // Si la flèche du haut ou la touche Z (vive ZQSD) est pressée, on avance + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Up) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Z)) + targetPos += cameraNode.GetForward() * cameraSpeed; + + // Si la flèche du bas ou la touche S est pressée, on recule + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Down) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::S)) + targetPos += cameraNode.GetBackward() * cameraSpeed; + + // Etc... + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Left) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Q)) + targetPos += cameraNode.GetLeft() * cameraSpeed; + + // Etc... + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Right) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::D)) + targetPos += cameraNode.GetRight() * cameraSpeed; + + // Majuscule pour monter, notez l'utilisation d'une direction globale (Non-affectée par la rotation) + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::LShift) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::RShift)) + targetPos += Nz::Vector3f::Up() * cameraSpeed; + + // Contrôle (Gauche ou droite) pour descendre dans l'espace global, etc... + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::LControl) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::RControl)) + targetPos += Nz::Vector3f::Down() * cameraSpeed; + + cameraNode.SetPosition((smoothMovement) ? DampedString(cameraNode.GetPosition(), targetPos, elapsedTime) : targetPos, Nz::CoordSys_Global); + + updateAccumulator = 0; + } + + // Et maintenant pour rendre la scène, il nous suffit de mettre à jour le monde en lui envoyant le temps depuis la dernière mise à jour + // Note: La plupart des systèmes, à l'exception de celui de rendu, ont une fréquence de mise à jour fixe (modifiable) + // Il n'est donc pas nécessaire de limiter vous-même les mises à jour du monde + world.Update(elapsedUS / 1000000.f); + + // 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 + { + // Et on insère ces données dans le titre de la fenêtre + window.SetTitle(windowTitle + " - " + Nz::String::Number(fps) + " FPS"); + + /* + 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 dans ce fichier), + 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; +} + +Nz::Vector3f DampedString(const Nz::Vector3f& currentPos, const Nz::Vector3f& targetPos, float frametime, float springStrength) +{ + // Je ne suis pas l'auteur de cette fonction + // Je l'ai reprise du programme "Floaty Camera Example" et adaptée au C++ + // Trouvé ici: http://nccastaff.bournemouth.ac.uk/jmacey/RobTheBloke/www/opengl_programming.html#4 + // Tout le mérite revient à l'auteur (Qui me permettra ainsi d'améliorer les démos, voire même le moteur) + + // calculate the displacement between the target and the current position + Nz::Vector3f displacement = targetPos - currentPos; + + // whats the distance between them? + float displacementLength = displacement.GetLength(); + + // Stops small position fluctuations (integration errors probably - since only using euler) + if (Nz::NumberEquals(displacementLength, 0.f)) + return currentPos; + + float invDisplacementLength = 1.f/displacementLength; + + const float dampConstant = 0.000065f; // Something v.small to offset 1/ displacement length + + // the strength of the spring increases the further away the camera is from the target. + float springMagitude = springStrength*displacementLength + dampConstant*invDisplacementLength; + + // Normalise the displacement and scale by the spring magnitude + // and the amount of time passed + float scalar = std::min(invDisplacementLength * springMagitude * frametime, 1.f); + displacement *= scalar; + + // move the camera a bit towards the target + return currentPos + displacement; +} diff --git a/include/Nazara/Audio/OpenAL.hpp b/include/Nazara/Audio/OpenAL.hpp index 5d6fa2634..eab95e6d0 100644 --- a/include/Nazara/Audio/OpenAL.hpp +++ b/include/Nazara/Audio/OpenAL.hpp @@ -87,7 +87,6 @@ namespace Nz static bool OpenDevice(); static OpenALFunc LoadEntry(const char* name, bool throwException = false); }; -} // al NAZARA_AUDIO_API extern OpenALDetail::LPALBUFFER3F alBuffer3f; @@ -186,6 +185,8 @@ NAZARA_AUDIO_API extern OpenALDetail::LPALCOPENDEVICE alcOpenDevice; NAZARA_AUDIO_API extern OpenALDetail::LPALCPROCESSCONTEXT alcProcessContext; NAZARA_AUDIO_API extern OpenALDetail::LPALCSUSPENDCONTEXT alcSuspendContext; +} + #endif // NAZARA_AUDIO_OPENAL #endif // NAZARA_OPENAL_HPP diff --git a/include/Nazara/Core/ResourceSaver.hpp b/include/Nazara/Core/ResourceSaver.hpp index be2880cd0..05b6d7175 100644 --- a/include/Nazara/Core/ResourceSaver.hpp +++ b/include/Nazara/Core/ResourceSaver.hpp @@ -27,6 +27,7 @@ namespace Nz friend Type; public: + using ExtensionGetter = bool (*)(const String& extension); using FormatQuerier = bool (*)(const String& format); using FileSaver = bool (*)(const Type& resource, const String& filePath, const Parameters& parameters); using StreamSaver = bool (*)(const Type& resource, const String& format, Stream& stream, const Parameters& parameters); diff --git a/include/Nazara/Core/ResourceSaver.inl b/include/Nazara/Core/ResourceSaver.inl index 954b86a0e..11d3f2a44 100644 --- a/include/Nazara/Core/ResourceSaver.inl +++ b/include/Nazara/Core/ResourceSaver.inl @@ -28,7 +28,7 @@ namespace Nz { for (Saver& saver : Type::s_savers) { - ExtensionGetter isExtensionSupported = std::get<0>(loader); + ExtensionGetter isExtensionSupported = std::get<0>(saver); if (isExtensionSupported && isExtensionSupported(extension)) return true; diff --git a/include/Nazara/Graphics/SkyboxBackground.hpp b/include/Nazara/Graphics/SkyboxBackground.hpp index 427548dc0..95ce2872a 100644 --- a/include/Nazara/Graphics/SkyboxBackground.hpp +++ b/include/Nazara/Graphics/SkyboxBackground.hpp @@ -30,7 +30,7 @@ namespace Nz SkyboxBackground(TextureRef cubemapTexture = TextureRef()); ~SkyboxBackground() = default; - void Draw(const AbstractViewer* viewer) const; + void Draw(const AbstractViewer* viewer) const override; BackgroundType GetBackgroundType() const override; inline const Vector3f& GetMovementOffset() const; diff --git a/include/Nazara/Math/BoundingVolume.inl b/include/Nazara/Math/BoundingVolume.inl index 15107e755..a1efd8364 100644 --- a/include/Nazara/Math/BoundingVolume.inl +++ b/include/Nazara/Math/BoundingVolume.inl @@ -636,7 +636,7 @@ namespace Nz if (extend > Extend_Max) return false; - boundingVolume->extend = extend; + boundingVolume->extend = static_cast(extend); if (!Unserialize(context, &boundingVolume->aabb)) return false; diff --git a/include/Nazara/Math/Frustum.hpp b/include/Nazara/Math/Frustum.hpp index e170c2e55..2e51c0bd6 100644 --- a/include/Nazara/Math/Frustum.hpp +++ b/include/Nazara/Math/Frustum.hpp @@ -55,6 +55,11 @@ namespace Nz String ToString() const; + template + friend bool Serialize(SerializationContext& context, const Frustum& frustum); + template + friend bool Unserialize(SerializationContext& context, Frustum* frustum); + private: Vector3 m_corners[BoxCorner_Max+1]; Plane m_planes[FrustumPlane_Max+1]; @@ -62,9 +67,6 @@ namespace Nz typedef Frustum Frustumd; typedef Frustum Frustumf; - - template bool Serialize(SerializationContext& context, const Frustum& frustum); - template bool Unserialize(SerializationContext& context, Frustum* frustum); } template diff --git a/include/Nazara/Math/Frustum.inl b/include/Nazara/Math/Frustum.inl index 2e78682d1..b9f56c1e1 100644 --- a/include/Nazara/Math/Frustum.inl +++ b/include/Nazara/Math/Frustum.inl @@ -688,13 +688,13 @@ namespace Nz { for (unsigned int i = 0; i <= BoxCorner_Max; ++i) { - if (!Serialize(context, m_corners[i])) + if (!Serialize(context, frustum.m_corners[i])) return false; } for (unsigned int i = 0; i <= FrustumPlane_Max; ++i) { - if (!Serialize(context, m_planes[i])) + if (!Serialize(context, frustum.m_planes[i])) return false; } @@ -702,24 +702,24 @@ namespace Nz } /*! - * \brief Unserializes a Matrix4 + * \brief Unserializes a Frustum * \return true if successfully unserialized * * \param context Serialization context - * \param matrix Output matrix + * \param matrix Output frustum */ template bool Unserialize(SerializationContext& context, Frustum* frustum) { for (unsigned int i = 0; i <= BoxCorner_Max; ++i) { - if (!Unserialize(context, &m_corners[i])) + if (!Unserialize(context, &frustum->m_corners[i])) return false; } for (unsigned int i = 0; i <= FrustumPlane_Max; ++i) { - if (!Unserialize(context, &m_planes[i])) + if (!Unserialize(context, &frustum->m_planes[i])) return false; } diff --git a/include/Nazara/Network/RUdpConnection.hpp b/include/Nazara/Network/RUdpConnection.hpp index 20285b6fc..2896ec6ce 100644 --- a/include/Nazara/Network/RUdpConnection.hpp +++ b/include/Nazara/Network/RUdpConnection.hpp @@ -119,6 +119,10 @@ namespace Nz struct PeerData //TODO: Move this to RUdpClient { + PeerData() = default; + PeerData(PeerData&& other) = default; + PeerData& operator=(PeerData&& other) = default; + std::array, PacketPriority_Max + 1> pendingPackets; std::deque pendingAckQueue; std::set receivedQueue; @@ -153,6 +157,6 @@ namespace Nz }; } -#include +#include -#endif // NAZARA_RUDPSERVER_HPP \ No newline at end of file +#endif // NAZARA_RUDPSERVER_HPP diff --git a/include/Nazara/Renderer/OpenGL.hpp b/include/Nazara/Renderer/OpenGL.hpp index 403a7a534..94d7f891d 100644 --- a/include/Nazara/Renderer/OpenGL.hpp +++ b/include/Nazara/Renderer/OpenGL.hpp @@ -153,7 +153,6 @@ namespace Nz static void OnContextChanged(const Context* newContext); static void OnContextDestruction(const Context* context); }; -} NAZARA_RENDERER_API extern PFNGLACTIVETEXTUREPROC glActiveTexture; NAZARA_RENDERER_API extern PFNGLATTACHSHADERPROC glAttachShader; @@ -336,6 +335,8 @@ NAZARA_RENDERER_API extern GLX::PFNGLXSWAPINTERVALMESAPROC NzglXSwapInter NAZARA_RENDERER_API extern GLX::PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI; #endif +} + #endif // NAZARA_RENDERER_OPENGL #endif // NAZARA_OPENGL_HPP diff --git a/src/Nazara/Audio/OpenAL.cpp b/src/Nazara/Audio/OpenAL.cpp index 1c30cd5f1..f527b682d 100644 --- a/src/Nazara/Audio/OpenAL.cpp +++ b/src/Nazara/Audio/OpenAL.cpp @@ -372,7 +372,6 @@ namespace Nz return entry; } -} // al OpenALDetail::LPALBUFFER3F alBuffer3f = nullptr; @@ -470,3 +469,5 @@ OpenALDetail::LPALCMAKECONTEXTCURRENT alcMakeContextCurrent = nullptr; OpenALDetail::LPALCOPENDEVICE alcOpenDevice = nullptr; OpenALDetail::LPALCPROCESSCONTEXT alcProcessContext = nullptr; OpenALDetail::LPALCSUSPENDCONTEXT alcSuspendContext = nullptr; + +} diff --git a/src/Nazara/Network/RUdpConnection.cpp b/src/Nazara/Network/RUdpConnection.cpp index 12fdc3d22..be2bec015 100644 --- a/src/Nazara/Network/RUdpConnection.cpp +++ b/src/Nazara/Network/RUdpConnection.cpp @@ -2,7 +2,7 @@ // This file is part of the "Nazara Engine - Utility module" // For conditions of distribution and use, see copyright notice in Config.hpp -#include +#include #include #include #include diff --git a/src/Nazara/Renderer/OpenGL.cpp b/src/Nazara/Renderer/OpenGL.cpp index 5cc0865ea..0834999bc 100644 --- a/src/Nazara/Renderer/OpenGL.cpp +++ b/src/Nazara/Renderer/OpenGL.cpp @@ -2078,7 +2078,6 @@ namespace Nz }; static_assert(VertexComponent_Max + 1 == 16, "Attribute index array is incomplete"); -} PFNGLACTIVETEXTUREPROC glActiveTexture = nullptr; PFNGLATTACHSHADERPROC glAttachShader = nullptr; @@ -2260,3 +2259,5 @@ GLX::PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = nullptr; GLX::PFNGLXSWAPINTERVALMESAPROC NzglXSwapIntervalMESA = nullptr; GLX::PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = nullptr; #endif + +} diff --git a/tests/Engine/Core/Serialization.cpp b/tests/Engine/Core/Serialization.cpp new file mode 100644 index 000000000..299b9cc8f --- /dev/null +++ b/tests/Engine/Core/Serialization.cpp @@ -0,0 +1,261 @@ +#include + +#include +#include +#include +#include +#include + +#include + +SCENARIO("Serialization", "[CORE][SERIALIZATION]") +{ + GIVEN("A context of serialization") + { + std::array datas; // The array must be bigger than any of the serializable classes + Nz::MemoryView stream(datas.data(), datas.size()); + + Nz::SerializationContext context; + context.stream = &stream; + + WHEN("We serialize basic types") + { + THEN("Arithmetical types") + { + context.stream->SetCursorPos(0); + REQUIRE(Serialize(context, 3)); + int value = 0; + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &value)); + REQUIRE(value == 3); + } + + THEN("Boolean type") + { + context.stream->SetCursorPos(0); + REQUIRE(Serialize(context, true)); + context.stream->SetCursorPos(0); + bool value = false; + REQUIRE(Unserialize(context, &value)); + REQUIRE(value == true); + } + } + + WHEN("We serialize mathematical classes") + { + THEN("BoudingVolume") + { + context.stream->SetCursorPos(0); + Nz::BoundingVolumef nullVolume = Nz::BoundingVolumef::Null(); + Nz::BoundingVolumef copy(nullVolume); + REQUIRE(Serialize(context, nullVolume)); + nullVolume = Nz::BoundingVolumef::Infinite(); + REQUIRE(nullVolume != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &nullVolume)); + REQUIRE(nullVolume == copy); + } + + THEN("Box") + { + context.stream->SetCursorPos(0); + Nz::Boxf zeroBox = Nz::Boxf::Zero(); + Nz::Boxf copy(zeroBox); + REQUIRE(Serialize(context, zeroBox)); + zeroBox = Nz::Boxf(1, 1, 1, 1, 1, 1); + REQUIRE(zeroBox != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &zeroBox)); + REQUIRE(zeroBox == copy); + } + + THEN("EulerAngles") + { + context.stream->SetCursorPos(0); + Nz::EulerAnglesf zeroEuler = Nz::EulerAnglesf::Zero(); + Nz::EulerAnglesf copy(zeroEuler); + REQUIRE(Serialize(context, zeroEuler)); + zeroEuler = Nz::EulerAnglesf(10, 24, 6); // Random values + REQUIRE(zeroEuler != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &zeroEuler)); + REQUIRE(zeroEuler == copy); + } + + THEN("Frustum") + { + context.stream->SetCursorPos(0); + Nz::Frustumf frustum; + frustum.Build(10, 10, 10, 100, Nz::Vector3f::UnitX(), Nz::Vector3f::UnitZ()); // Random values + Nz::Frustumf copy(frustum); + REQUIRE(Serialize(context, frustum)); + frustum.Build(50, 40, 20, 100, Nz::Vector3f::UnitX(), Nz::Vector3f::UnitZ()); + for (unsigned int i = 0; i <= Nz::BoxCorner_Max; ++i) + REQUIRE(frustum.GetCorner(static_cast(i)) != copy.GetCorner(static_cast(i))); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &frustum)); + for (unsigned int i = 0; i <= Nz::BoxCorner_Max; ++i) + REQUIRE(frustum.GetCorner(static_cast(i)) == copy.GetCorner(static_cast(i))); + } + + THEN("Matrix4") + { + context.stream->SetCursorPos(0); + Nz::Matrix4f zeroMatrix = Nz::Matrix4f::Zero(); + Nz::Matrix4f copy(zeroMatrix); + REQUIRE(Serialize(context, zeroMatrix)); + zeroMatrix = Nz::Matrix4f::Identity(); // Random values + REQUIRE(zeroMatrix != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &zeroMatrix)); + REQUIRE(zeroMatrix == copy); + } + + THEN("OrientedBox") + { + context.stream->SetCursorPos(0); + Nz::OrientedBoxf zeroOBB = Nz::OrientedBoxf::Zero(); + Nz::OrientedBoxf copy(zeroOBB); + REQUIRE(Serialize(context, zeroOBB)); + zeroOBB = Nz::OrientedBoxf(1, 1, 1, 1, 1, 1); // Random values + REQUIRE(zeroOBB != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &zeroOBB)); + REQUIRE(zeroOBB == copy); + } + + THEN("Plane") + { + context.stream->SetCursorPos(0); + Nz::Planef planeXY = Nz::Planef::XY(); + Nz::Planef copy(planeXY); + REQUIRE(Serialize(context, planeXY)); + planeXY = Nz::Planef::YZ(); + REQUIRE(planeXY != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &planeXY)); + REQUIRE(planeXY == copy); + } + + THEN("Quaternion") + { + context.stream->SetCursorPos(0); + Nz::Quaternionf quaternionIdentity = Nz::Quaternionf::Identity(); + Nz::Quaternionf copy(quaternionIdentity); + REQUIRE(Serialize(context, quaternionIdentity)); + quaternionIdentity = Nz::Quaternionf::Zero(); + REQUIRE(quaternionIdentity != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &quaternionIdentity)); + REQUIRE(quaternionIdentity == copy); + } + + THEN("Ray") + { + context.stream->SetCursorPos(0); + Nz::Rayf axisX = Nz::Rayf::AxisX(); + Nz::Rayf copy(axisX); + REQUIRE(Serialize(context, axisX)); + axisX = Nz::Rayf::AxisY(); + REQUIRE(axisX != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &axisX)); + REQUIRE(axisX == copy); + } + + THEN("Rect") + { + context.stream->SetCursorPos(0); + Nz::Rectf zeroRect = Nz::Rectf::Zero(); + Nz::Rectf copy(zeroRect); + REQUIRE(Serialize(context, zeroRect)); + zeroRect = Nz::Rectf(1, 1, 1, 1); // Random values + REQUIRE(zeroRect != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &zeroRect)); + REQUIRE(zeroRect == copy); + } + + THEN("Sphere") + { + context.stream->SetCursorPos(0); + Nz::Spheref zeroSphere = Nz::Spheref::Zero(); + Nz::Spheref copy(zeroSphere); + REQUIRE(Serialize(context, zeroSphere)); + zeroSphere = Nz::Spheref::Unit(); + REQUIRE(zeroSphere != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &zeroSphere)); + REQUIRE(zeroSphere == copy); + } + + THEN("Vector2") + { + context.stream->SetCursorPos(0); + Nz::Vector2f unitX = Nz::Vector2f::UnitX(); + Nz::Vector2f copy(unitX); + REQUIRE(Serialize(context, unitX)); + unitX = Nz::Vector2f::UnitY(); + REQUIRE(unitX != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &unitX)); + REQUIRE(unitX == copy); + } + + THEN("Vector3") + { + context.stream->SetCursorPos(0); + Nz::Vector3f unitX = Nz::Vector3f::UnitX(); + Nz::Vector3f copy(unitX); + REQUIRE(Serialize(context, unitX)); + unitX = Nz::Vector3f::UnitY(); + REQUIRE(unitX != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &unitX)); + REQUIRE(unitX == copy); + } + + THEN("Vector4") + { + context.stream->SetCursorPos(0); + Nz::Vector4f unitX = Nz::Vector4f::UnitX(); + Nz::Vector4f copy(unitX); + REQUIRE(Serialize(context, unitX)); + unitX = Nz::Vector4f::UnitY(); + REQUIRE(unitX != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &unitX)); + REQUIRE(unitX == copy); + } + } + + WHEN("We serialize core classes") + { + THEN("Color") + { + context.stream->SetCursorPos(0); + Nz::Color red = Nz::Color::Red; + Nz::Color copy(red); + REQUIRE(Serialize(context, red)); + red = Nz::Color::Black; + REQUIRE(red != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &red)); + REQUIRE(red == copy); + } + + THEN("String") + { + context.stream->SetCursorPos(0); + Nz::String string = "string"; + Nz::String copy(string); + REQUIRE(Serialize(context, string)); + string = "another"; + REQUIRE(string != copy); + context.stream->SetCursorPos(0); + REQUIRE(Unserialize(context, &string)); + REQUIRE(string == copy); + } + } + } +} diff --git a/tests/resources/Engine/Core/FileTest.txt b/tests/resources/Engine/Core/FileTest.txt new file mode 100644 index 000000000..345e6aef7 --- /dev/null +++ b/tests/resources/Engine/Core/FileTest.txt @@ -0,0 +1 @@ +Test From 91f2bee48747e1fd05aff2eef1bd0184ba740e55 Mon Sep 17 00:00:00 2001 From: Gawaboumga Date: Mon, 4 Apr 2016 10:36:13 +0200 Subject: [PATCH 2/2] Bug fix -> String with one char + Directory and File on linux Former-commit-id: 7f9b6c44197c3cc67145eb0a2d421a2e1de45a84 --- examples/Mario/main.cpp | 447 ------------------------ include/Nazara/Math/Matrix4.inl | 3 +- src/Nazara/Core/Posix/DirectoryImpl.cpp | 2 +- src/Nazara/Core/Posix/FileImpl.cpp | 6 +- src/Nazara/Core/String.cpp | 8 +- src/Nazara/Network/RUdpConnection.cpp | 2 +- tests/Engine/Core/File.cpp | 36 +- tests/Engine/Core/String.cpp | 15 + 8 files changed, 60 insertions(+), 459 deletions(-) delete mode 100644 examples/Mario/main.cpp diff --git a/examples/Mario/main.cpp b/examples/Mario/main.cpp deleted file mode 100644 index 24fef574f..000000000 --- a/examples/Mario/main.cpp +++ /dev/null @@ -1,447 +0,0 @@ -/* -** FirstScene - Première scène graphique -** Prérequis: Aucun -** Utilisation du module utilitaire et graphique -** Présente: -** - Création et gestion d'une fenêtre (Traitement des évènements clavier/souris) -** - Gestion du clavier (Récupération de l'état d'une touche) -** - Des outils pour afficher une scène basique via le chargement d'un modèle (et son affichage) -** - Éclairage directionnel -** - Gestion d'une caméra free-fly (Avec déplacement fluide) -** - Gestion basique d'une horloge -*/ - -#include // Horloges -#include // Module graphique -#include // Module de rendu -#include // Module utilitaire -#include -#include -#include -#include -#include -#include -#include -#include - -// Petite fonction permettant de rendre le déplacement de la caméra moins ridige -Nz::Vector3f DampedString(const Nz::Vector3f& currentPos, const Nz::Vector3f& targetPos, float frametime, float springStrength = 3.f); - -int main() -{ - // Pour commencer, nous initialisons le SDK de Nazara, celui-ci va préparer le terrain en initialisant le moteur, - // les composants, systèmes, etc. - // 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. - Nz::Initializer 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 le monde pour contenir notre scène. - // Dans un ECS, le monde représente bien ce que son nom indique, c'est l'ensemble de ce qui existe au niveau de l'application. - // Il contient les systèmes et les entités, ces dernières contiennent les composants. - // Il est possible d'utiliser plusieurs mondes au sein d'une même application, par exemple pour gérer un mélange de 2D et de 3D, - // mais nous verrons cela dans un prochain exemple. - Ndk::World world; - - // Nous pouvons maintenant ajouter des systèmes, mais dans cet exemple nous nous contenterons de ceux de base. - - // La première chose que nous faisons est d'ajouter un background (fond) à notre scène. - // Il en existe plusieurs types, le moteur inclut pour l'instant trois d'entre eux: - // -ColorBackground: Une couleur unie en fond - // -SkyboxBackground: Une skybox en fond, un cube à six faces rendu autour de la caméra (En perdant la notion de distance) - // -TextureBackground: Une texture en fond, celle-ci sera affichée derrière la scène - - // Nous choisirons ici une Skybox, cette dernière étant l'effet le plus réussi et convenant très bien à une scène spatiale - // Pour commencer il faut charger une texture de type cubemap, certaines images sont assemblées de cette façon, - // comme celle que nous allons utiliser. - // En réalité les textures "cubemap" regroupent six faces en une, pour faciliter leur utilisation. - - // Nous créons une nouvelle texture et prenons une référence sur celle-ci (à la manière des pointeurs intelligents) - Nz::TextureRef texture = Nz::Texture::New(); - if (texture->LoadCubemapFromFile("resources/skybox-space.png")) - { - // Si la création du cubemap a fonctionné - - // Nous créons alors le background à partir de notre texture (celui-ci va référencer notre texture, notre pointeur ne sert alors plus à rien). - Nz::SkyboxBackgroundRef skybox = Nz::SkyboxBackground::New(std::move(texture)); - - // Accédons maintenant au système de rendu faisant partie du monde - Ndk::RenderSystem& renderSystem = world.GetSystem(); // Une assertion valide la précondition "le système doit faire partie du monde" - - // Nous assignons ensuite notre skybox comme "fond par défaut" du système - // La notion "par défaut" existe parce qu'une caméra pourrait utiliser son propre fond lors du rendu, - // le fond par défaut est utilisé lorsque la caméra n'a pas de fond propre assigné - renderSystem.SetDefaultBackground(std::move(skybox)); - - // Notre skybox est maintenant référencée par le système, lui-même appartenant au monde, aucune libération explicite n'est nécessaire - } - else - // Le chargement a échoué - std::cout << "Failed to load skybox" << std::endl; - - // Ensuite, nous allons rajouter un modèle à notre scène. - - // Les modèles représentent, globalement, tout ce qui est visible en trois dimensions. - // Nous choisirons ici un vaisseau spatial (Quoi de mieux pour une scène spatiale ?) - - // Encore une fois, nous récupérons une référence plutôt que l'objet lui-même (cela va être très utile par la suite) - Nz::ModelRef spaceshipModel = Nz::Model::New(); - - // Nous allons charger notre modèle depuis un fichier, mais nous pouvons ajuster le modèle lors du chargement via - // une structure permettant de paramétrer le chargement des modèles - Nz::ModelParameters params; - - // Le format OBJ ne précise aucune échelle pour ses données, contrairement à Nazara (une unité = un mètre en 3D). - // Comme le vaisseau est très grand (Des centaines de mètres de long), nous allons le rendre plus petit pour les besoins de la démo. - // Ce paramètre sert à indiquer la mise à l'échelle désirée lors du chargement du modèle. - params.mesh.scale.Set(0.01f); // Un centième de la taille originelle - - // Les UVs de ce fichier sont retournées (repère OpenGL, origine coin bas-gauche) par rapport à ce que le moteur attend (haut-gauche) - // Nous devons donc indiquer au moteur de les retourner lors du chargement - params.mesh.flipUVs = true; - - // Nazara va par défaut optimiser les modèles pour un rendu plus rapide, cela peut prendre du temps et n'est pas nécessaire ici - params.mesh.optimizeIndexBuffers = false; - - // On charge ensuite le modèle depuis son fichier - // Le moteur va charger le fichier et essayer de retrouver les fichiers associés (comme les matériaux, textures, ...) - if (!spaceshipModel->LoadFromFile("resources/Spaceship/spaceship.obj", params)) - { - // Si le chargement a échoué (fichier inexistant/invalide), il ne sert à rien de continuer - std::cout << "Failed to load spaceship" << std::endl; - std::getchar(); - - return EXIT_FAILURE; - } - - // Nous voulons afficher quelques statistiques relatives au modèle, comme le nombre de sommets et de triangles - // Pour cela, nous devons accéder au mesh (maillage 3D) - - // Note: Si nous voulions stocker le mesh pour nous en servir après, nous devrions alors récupérer une référence pour nous assurer - // qu'il ne sera pas supprimé tant que nous l'utilisons, mais ici nous faisons un accès direct et ne nous servons plus du pointeur par la suite - // Il est donc acceptable d'utiliser un pointeur nu ici. - Nz::Mesh* mesh = spaceshipModel->GetMesh(); - std::cout << mesh->GetVertexCount() << " sommets" << std::endl; - std::cout << mesh->GetTriangleCount() << " triangles" << std::endl; - - // En revanche, le format OBJ ne précise pas l'utilisation d'une normal map, nous devons donc la charger manuellement - // Pour commencer on récupère le matériau du mesh, celui-ci en possède plusieurs mais celui qui nous intéresse, - // celui de la coque, est le second (Cela est bien entendu lié au modèle en lui-même) - Nz::Material* material = spaceshipModel->GetMaterial(1); // Encore une fois nous ne faisons qu'un accès direct. - - // On lui indique ensuite le chemin vers la normal map - if (!material->SetNormalMap("resources/Spaceship/Texture/normal.png")) - { - // Le chargement a échoué, peut-être le fichier n'existe pas, ou n'est pas reconnu par le moteur - // Mais ce n'est pas une erreur critique, le rendu peut quand même se faire (Mais sera moins détaillé) - std::cout << "Failed to load normal map" << std::endl; - } - - // Bien, nous avons un modèle valide, mais celui-ci ne consiste qu'en des informations de rendu, de matériaux et de textures. - // Commençons donc par créer une entité vide, cela se fait en demandant au monde de générer une nouvelle entité. - Ndk::EntityHandle spaceship = world.CreateEntity(); - - // Note: Nous ne récupérons pas l'entité directement mais un "handle" vers elle, ce dernier est un pointeur intelligent non-propriétaire. - // Pour des raisons techniques, le pointeur de l'entité peut venir à changer, ou l'entité être simplement détruite pour n'importe quelle raison. - // Le Handle nous permet de maintenir un pointeur valide vers notre entité, et invalidé automatiquement à sa mort. - - // Nous avons désormais une entité, mais celle-ci ne contient rien et n'a d'autre propriété qu'un identifiant - // Nous devons donc lui rajouter les composants que nous voulons. - - // Un NodeComponent donne à notre entité une position, rotation, échelle, et nous permet de l'attacher à d'autres entités (ce que nous ne ferons pas ici). - // Étant donné que par défaut, un NodeComponent se place en (0,0,0) sans rotation et avec une échelle de 1,1,1 et que cela nous convient, - // nous n'avons pas besoin d'agir sur le composant créé. - spaceship->AddComponent(); - - // Bien, notre entité nouvellement créé dispose maintenant d'une position dans la scène, mais est toujours invisible - // Nous lui ajoutons donc un GraphicsComponent - Ndk::GraphicsComponent& spaceshipGraphics = spaceship->AddComponent(); - - // Ce composant sert de point d'attache pour tous les renderables instanciés (tels que les modèles, les sprites, le texte, etc.) - // Cela signifie également qu'un modèle peut être attaché à autant d'entités que nécessaire. - // Note: Afin de maximiser les performances, essayez d'avoir le moins de renderables instanciés/matériaux et autres ressources possible - // le moteur fonctionne selon le batching et regroupera par exemple tous les modèles identiques ensembles lors du rendu. - spaceshipGraphics.Attach(spaceshipModel); - - // Nous avons besoin également d'une caméra pour servir de point de vue à notre scène, celle-ci sera à l'écart du modèle - // regardant dans sa direction. - - // On conserve la rotation à part via des angles d'eulers pour la caméra free-fly - Nz::EulerAnglesf camAngles(0.f, -20.f, 0.f); - - // Nous créons donc une seconde entité - // Note: La création d'entité est une opération légère au sein du moteur, mais plus vous aurez d'entités et plus le processeur devra travailler. - Ndk::EntityHandle camera = world.CreateEntity(); - - // Notre caméra a elle aussi besoin d'être positionnée dans la scène - Ndk::NodeComponent& cameraNode = camera->AddComponent(); - cameraNode.SetPosition(0.f, 0.25f, 2.f); // On place la caméra à l'écart - cameraNode.SetRotation(camAngles); - - // Et dispose d'un composant pour chaque point de vue de la scène, le CameraComponent - Ndk::CameraComponent& cameraComp = camera->AddComponent(); - - // 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) - - // La distance entre l'oeil et le plan éloigné - cameraComp.SetZFar(5000.f); - - // La distance entre l'oeil et le plan rapproché (0 est une valeur interdite car la division par zéro l'est également) - cameraComp.SetZNear(0.1f); - - // Attention que le ratio entre les deux (zFar/zNear) doit rester raisonnable, dans le cas contraire vous risquez un phénomène - // de "Z-Fighting" (Impossibilité de déduire quelle surface devrait apparaître en premier) sur les surfaces éloignées. - - // Il ne nous manque plus maintenant que de l'éclairage, sans quoi la scène sera complètement noire - // Il existe trois types de lumières: - // -DirectionalLight: Lumière infinie sans position, envoyant de la lumière dans une direction particulière - // -PointLight: Lumière située à un endroit précis, envoyant de la lumière finie dans toutes les directions - // -SpotLight: Lumière située à un endroit précis, envoyant de la lumière vers un endroit donné, avec un angle de diffusion - - // Nous allons créer une lumière directionnelle pour représenter la nébuleuse de notre skybox - // Encore une fois, nous créons notre entité - Ndk::EntityHandle nebulaLight = world.CreateEntity(); - - // Lui ajoutons une position dans la scène - Ndk::NodeComponent& nebulaLightNode = nebulaLight->AddComponent(); - - // Et ensuite le composant principal, le LightComponent - Ndk::LightComponent& nebulaLightComp = nebulaLight->AddComponent(Nz::LightType_Directional); - - // Il nous faut ensuite configurer la lumière - // Pour commencer, sa couleur, la nébuleuse étant d'une couleur jaune, j'ai choisi ces valeurs - nebulaLightComp.SetColor(Nz::Color(255, 182, 90)); - - // Nous appliquons ensuite une rotation de sorte que la lumière dans la même direction que la nébuleuse - nebulaLightNode.SetRotation(Nz::EulerAnglesf(0.f, 102.f, 0.f)); - - // Nous allons maintenant créer la fenêtre, dans laquelle nous ferons nos rendus - // Celle-ci demande des paramètres 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 - Nz::VideoMode mode = Nz::VideoMode::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 * mode.width / 4; - mode.height = 3 * mode.height / 4; - - // Maintenant le titre, rien de plus simple... - Nz::String windowTitle = "Nazara Demo - First scene"; - - // Ensuite, le "style" de la fenêtre, possède-t-elle des bordures, peut-on cliquer sur la croix de fermeture, - // peut-on la redimensionner, ... - Nz::WindowStyleFlags style = Nz::WindowStyle_Default; // Nous prenons le style par défaut, autorisant tout ce que je viens de citer - - // Ensuite, les paramètres du contexte de rendu - // On peut configurer le niveau d'antialiasing, le nombre de bits du depth buffer et le nombre de bits du stencil buffer - // Nous désirons avoir un peu d'antialiasing (4x), les valeurs par défaut pour le reste nous conviendrons très bien - Nz::RenderTargetParameters parameters; - parameters.antialiasingLevel = 4; - - Nz::RenderWindow window(mode, windowTitle, style, parameters); - 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(Nz::WindowCursor_None); - - // On lie la caméra à la fenêtre - cameraComp.SetTarget(&window); - - // Et on créé deux horloges pour gérer le temps - Nz::Clock secondClock, updateClock; - Nz::UInt64 updateAccumulator = 0; - - // Ainsi qu'un compteur de FPS improvisé - unsigned int fps = 0; - - // Quelques variables de plus pour notre caméra - bool smoothMovement = true; - Nz::Vector3f targetPos = cameraNode.GetPosition(); - - // 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) - Nz::WindowEvent event; - while (window.PollEvent(&event)) - { - switch (event.type) - { - case Nz::WindowEventType_MouseMoved: // La souris a bougé - { - // Gestion de la caméra free-fly (Rotation) - float sensitivity = 0.3f; // 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 = Nz::NormalizeAngle(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 = Nz::Clamp(camAngles.pitch - event.mouseMove.deltaY*sensitivity, -89.f, 89.f); - - // On applique les angles d'Euler à notre caméra - cameraNode.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 - Nz::Mouse::SetPosition(window.GetWidth()/2, window.GetHeight()/2, window); - break; - } - - case Nz::WindowEventType_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 Nz::WindowEventType_KeyPressed: // Une touche a été pressée ! - if (event.key.code == Nz::Keyboard::Key::Escape) - window.Close(); - else if (event.key.code == Nz::Keyboard::F1) - { - if (smoothMovement) - { - targetPos = cameraNode.GetPosition(); - smoothMovement = false; - } - else - smoothMovement = true; - } - break; - - default: - break; - } - } - - Nz::UInt64 elapsedUS = updateClock.GetMicroseconds(); - // On relance l'horloge - updateClock.Restart(); - - // Mise à jour (Caméra) - const Nz::UInt64 updateRate = 1000000 / 60; // 60 fois par seconde - updateAccumulator += elapsedUS; - - if (updateAccumulator >= updateRate) - { - // Le temps écoulé en seconde depuis la dernière fois que ce bloc a été exécuté - float elapsedTime = updateAccumulator / 1000000.f; - std::cout << elapsedTime << std::endl; - - // Vitesse de déplacement de la caméra - float cameraSpeed = 3.f * elapsedTime; // Trois mètres par seconde - - // Si la touche espace est enfoncée, notre vitesse de déplacement est multipliée par deux - if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Space)) - cameraSpeed *= 2.f; - - // Pour que nos déplacement soient liés à la rotation de la caméra, nous allons utiliser - // les directions locales de la caméra - - // Si la flèche du haut ou la touche Z (vive ZQSD) est pressée, on avance - if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Up) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Z)) - targetPos += cameraNode.GetForward() * cameraSpeed; - - // Si la flèche du bas ou la touche S est pressée, on recule - if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Down) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::S)) - targetPos += cameraNode.GetBackward() * cameraSpeed; - - // Etc... - if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Left) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Q)) - targetPos += cameraNode.GetLeft() * cameraSpeed; - - // Etc... - if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Right) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::D)) - targetPos += cameraNode.GetRight() * cameraSpeed; - - // Majuscule pour monter, notez l'utilisation d'une direction globale (Non-affectée par la rotation) - if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::LShift) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::RShift)) - targetPos += Nz::Vector3f::Up() * cameraSpeed; - - // Contrôle (Gauche ou droite) pour descendre dans l'espace global, etc... - if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::LControl) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::RControl)) - targetPos += Nz::Vector3f::Down() * cameraSpeed; - - cameraNode.SetPosition((smoothMovement) ? DampedString(cameraNode.GetPosition(), targetPos, elapsedTime) : targetPos, Nz::CoordSys_Global); - - updateAccumulator = 0; - } - - // Et maintenant pour rendre la scène, il nous suffit de mettre à jour le monde en lui envoyant le temps depuis la dernière mise à jour - // Note: La plupart des systèmes, à l'exception de celui de rendu, ont une fréquence de mise à jour fixe (modifiable) - // Il n'est donc pas nécessaire de limiter vous-même les mises à jour du monde - world.Update(elapsedUS / 1000000.f); - - // 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 - { - // Et on insère ces données dans le titre de la fenêtre - window.SetTitle(windowTitle + " - " + Nz::String::Number(fps) + " FPS"); - - /* - 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 dans ce fichier), - 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; -} - -Nz::Vector3f DampedString(const Nz::Vector3f& currentPos, const Nz::Vector3f& targetPos, float frametime, float springStrength) -{ - // Je ne suis pas l'auteur de cette fonction - // Je l'ai reprise du programme "Floaty Camera Example" et adaptée au C++ - // Trouvé ici: http://nccastaff.bournemouth.ac.uk/jmacey/RobTheBloke/www/opengl_programming.html#4 - // Tout le mérite revient à l'auteur (Qui me permettra ainsi d'améliorer les démos, voire même le moteur) - - // calculate the displacement between the target and the current position - Nz::Vector3f displacement = targetPos - currentPos; - - // whats the distance between them? - float displacementLength = displacement.GetLength(); - - // Stops small position fluctuations (integration errors probably - since only using euler) - if (Nz::NumberEquals(displacementLength, 0.f)) - return currentPos; - - float invDisplacementLength = 1.f/displacementLength; - - const float dampConstant = 0.000065f; // Something v.small to offset 1/ displacement length - - // the strength of the spring increases the further away the camera is from the target. - float springMagitude = springStrength*displacementLength + dampConstant*invDisplacementLength; - - // Normalise the displacement and scale by the spring magnitude - // and the amount of time passed - float scalar = std::min(invDisplacementLength * springMagitude * frametime, 1.f); - displacement *= scalar; - - // move the camera a bit towards the target - return currentPos + displacement; -} diff --git a/include/Nazara/Math/Matrix4.inl b/include/Nazara/Math/Matrix4.inl index afa8143e3..b0decee2a 100644 --- a/include/Nazara/Math/Matrix4.inl +++ b/include/Nazara/Math/Matrix4.inl @@ -1792,9 +1792,10 @@ namespace Nz template bool Unserialize(SerializationContext& context, Matrix4* matrix) { + T* head = matrix->operator T*(); for (unsigned int i = 0; i < 16; ++i) { - if (!Unserialize(context, &matrix[i])) + if (!Unserialize(context, head + i)) return false; } diff --git a/src/Nazara/Core/Posix/DirectoryImpl.cpp b/src/Nazara/Core/Posix/DirectoryImpl.cpp index e3add7fd5..e23c82626 100644 --- a/src/Nazara/Core/Posix/DirectoryImpl.cpp +++ b/src/Nazara/Core/Posix/DirectoryImpl.cpp @@ -49,7 +49,7 @@ namespace Nz return true; else { - if (errno != ENOENT) + if (errno == EBADF || errno == EOVERFLOW) NazaraError("Unable to get next result: " + Error::GetLastSystemError()); return false; diff --git a/src/Nazara/Core/Posix/FileImpl.cpp b/src/Nazara/Core/Posix/FileImpl.cpp index 3fa93f6f2..31c1ace73 100644 --- a/src/Nazara/Core/Posix/FileImpl.cpp +++ b/src/Nazara/Core/Posix/FileImpl.cpp @@ -54,11 +54,11 @@ namespace Nz int flags; mode_t permissions = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; - if (mode & OpenMode_ReadWrite) + if ((mode & OpenMode_ReadWrite) == OpenMode_ReadWrite) flags = O_CREAT | O_RDWR; - else if (mode & OpenMode_ReadOnly) + else if ((mode & OpenMode_ReadOnly) == OpenMode_ReadOnly) flags = O_RDONLY; - else if (mode & OpenMode_WriteOnly) + else if ((mode & OpenMode_WriteOnly) == OpenMode_WriteOnly) flags = O_CREAT | O_WRONLY; else return false; diff --git a/src/Nazara/Core/String.cpp b/src/Nazara/Core/String.cpp index 3e72e1dce..b94715457 100644 --- a/src/Nazara/Core/String.cpp +++ b/src/Nazara/Core/String.cpp @@ -3204,14 +3204,12 @@ namespace Nz EnsureOwnership(true); m_sharedString->size = 1; - m_sharedString->string[0] = character; m_sharedString->string[1] = '\0'; } else - { - auto newString = std::make_shared(1); - newString->string[0] = character; - } + m_sharedString = std::make_shared(1); + + m_sharedString->string[0] = character; } else ReleaseString(); diff --git a/src/Nazara/Network/RUdpConnection.cpp b/src/Nazara/Network/RUdpConnection.cpp index be2bec015..f8b57cf00 100644 --- a/src/Nazara/Network/RUdpConnection.cpp +++ b/src/Nazara/Network/RUdpConnection.cpp @@ -474,7 +474,7 @@ namespace Nz return true; } - inline void RUdpConnection::Uninitialize() + void RUdpConnection::Uninitialize() { } diff --git a/tests/Engine/Core/File.cpp b/tests/Engine/Core/File.cpp index c00af74d1..2a87c8495 100644 --- a/tests/Engine/Core/File.cpp +++ b/tests/Engine/Core/File.cpp @@ -9,7 +9,7 @@ SCENARIO("File", "[CORE][FILE]") { Nz::File file("Test File.txt", Nz::OpenMode_ReadWrite); REQUIRE(file.GetDirectory() == Nz::Directory::GetCurrent() + NAZARA_DIRECTORY_SEPARATOR); - CHECK(file.IsOpen()); + REQUIRE(file.IsOpen()); THEN("We are allowed to write 3 times 'Test String'") { @@ -50,4 +50,38 @@ SCENARIO("File", "[CORE][FILE]") } } } + + GIVEN("The test file") + { + REQUIRE(Nz::File::Exists("resources/Engine/Core/FileTest.txt")); + + Nz::File fileTest("resources/Engine/Core/FileTest.txt", Nz::OpenMode_ReadOnly | Nz::OpenMode_Text); + + WHEN("We read the first line of the file") + { + REQUIRE(fileTest.IsOpen()); + Nz::String content = fileTest.ReadLine(); + + THEN("The content must be 'Test'") + { + REQUIRE(content == "Test"); + } + } + } + + GIVEN("Nothing") + { + WHEN("We get the absolute path of something containing relative path") + { + Nz::String containingDot = "/resources/Spaceship/./spaceship.mtl"; + Nz::String containingDoubleDot = "/resources/Spaceship/../Spaceship/spaceship.mtl"; + + THEN("The relative positioning should disappear") + { + Nz::String containingNoMoreDot = "/resources/Spaceship/spaceship.mtl"; + REQUIRE(Nz::File::AbsolutePath(containingDot) == containingNoMoreDot); + REQUIRE(Nz::File::AbsolutePath(containingDoubleDot) == containingNoMoreDot); + } + } + } } diff --git a/tests/Engine/Core/String.cpp b/tests/Engine/Core/String.cpp index a563d0b7f..eac5eaef6 100644 --- a/tests/Engine/Core/String.cpp +++ b/tests/Engine/Core/String.cpp @@ -82,6 +82,21 @@ SCENARIO("String", "[CORE][STRING]") } } + GIVEN("One character string") + { + Nz::String characterString; + + WHEN("We set the string to one character") + { + characterString.Set('/'); + + THEN("The string must contain it") + { + REQUIRE(characterString == '/'); + } + } + } + /* TODO GIVEN("One unicode string") {