From bb99d044bb15dc71b7a95cd4176bb3a05e332794 Mon Sep 17 00:00:00 2001 From: Lynix Date: Thu, 13 Jun 2013 19:29:59 +0200 Subject: [PATCH] Nazara now use meters as units Added scale mesh parameter Added smooth camera to FirstScene demo Former-commit-id: b3985e10d84512e3b32f2569ac034ba63ace589a --- examples/FirstScene/main.cpp | 99 +++++++++++++++---- include/Nazara/Utility/Mesh.hpp | 3 + src/Nazara/Graphics/Loaders/OBJ/Loader.cpp | 3 +- src/Nazara/Utility/Loaders/MD2/Loader.cpp | 4 + src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp | 16 ++- src/Nazara/Utility/Mesh.cpp | 6 ++ 6 files changed, 109 insertions(+), 22 deletions(-) diff --git a/examples/FirstScene/main.cpp b/examples/FirstScene/main.cpp index f1ed92186..aeb031068 100644 --- a/examples/FirstScene/main.cpp +++ b/examples/FirstScene/main.cpp @@ -4,6 +4,9 @@ #include // Module utilitaire #include +// Petite fonction permettant de rendre le déplacement de la caméra moins ridige +NzVector3f DampedString(const NzVector3f& currentPos, const NzVector3f& targetPos, float frametime, float springStrength = 3.f); + int main() { // Pour commencer, nous initialisons le module Graphique, celui-ci va provoquer l'initialisation (dans l'ordre), @@ -24,7 +27,7 @@ int main() // 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 + // Par exemple, une pièce contenant une télévision, laquelle affichant des images provenant d'une Camera // 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. @@ -70,9 +73,19 @@ int main() // Nous choisirons ici un vaisseau spatial (Quoi de mieux pour une scène spatiale ?) NzModel spaceship; + // Une structure permettant de paramétrer le chargement des modèles + NzModelParameters params; + + + // Le format OBJ ne précise aucune échelle pour ses données, contrairement à Nazara (une unité = un mètre). + // 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 + // 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 (!spaceship.LoadFromFile("resources/Spaceship/spaceship.obj")) + if (!spaceship.LoadFromFile("resources/Spaceship/spaceship.obj", params)) { std::cout << "Failed to load spaceship" << std::endl; std::getchar(); @@ -84,7 +97,7 @@ int main() // Pour cela, nous devons accéder au mesh (maillage 3D) NzMesh* mesh = spaceship.GetMesh(); - std::cout << mesh->GetVertexCount() << " vertices" << std::endl; + 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 @@ -102,7 +115,7 @@ int main() // Il nous reste à attacher le modèle à la scène, ce qui se fait simplement via cet appel spaceship.SetParent(scene); - // Et voilà, à partir de maintenant le modèle fait partie de la "hiérarchie de la scène", et sera donc rendu avec la scène + // Et voilà, à partir de maintenant le modèle fait partie de la hiérarchie de la scène, et sera donc rendu avec cette dernière // Nous avons besoin également d'une caméra, pour des raisons évidentes, celle-ci sera à l'écart du modèle // regardant dans sa direction. @@ -111,7 +124,7 @@ int main() NzEulerAnglesf camAngles(0.f, -20.f, 0.f); NzCamera camera; - camera.SetPosition(0.f, 25.f, 200.f); // On place la caméra à l'écart + camera.SetPosition(0.f, 0.25f, 2.f); // On place la caméra à l'écart camera.SetRotation(camAngles); camera.SetParent(scene); // On l'attache également à la scène @@ -122,7 +135,7 @@ int main() camera.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) - camera.SetZNear(1.f); + camera.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. @@ -198,6 +211,10 @@ int main() // Ainsi qu'un compteur de FPS improvisé unsigned int fps = 0; + // Quelques variables de plus pour notre caméra + bool smoothMovement = true; + NzVector3f targetPos = camera.GetPosition(); + // Début de la boucle de rendu du programme while (window.IsOpen()) { @@ -210,7 +227,7 @@ int main() case nzEventType_MouseMoved: // La souris a bougé { // Gestion de la caméra free-fly (Rotation) - float sensitivity = 0.8f; // Sensibilité de la souris + 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 = NzNormalizeAngle(camAngles.yaw - event.mouseMove.deltaX*sensitivity); @@ -234,6 +251,16 @@ int main() case nzEventType_KeyPressed: // Une touche a été pressée ! if (event.key.code == NzKeyboard::Key::Escape) window.Close(); + else if (event.key.code == NzKeyboard::F1) + { + if (smoothMovement) + { + targetPos = camera.GetPosition(); + smoothMovement = false; + } + else + smoothMovement = true; + } break; default: @@ -244,39 +271,44 @@ int main() // 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 + // Le temps écoulé depuis la dernière fois que ce bloc a été exécuté float elapsedTime = updateClock.GetSeconds(); + // 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 (NzKeyboard::IsKeyPressed(NzKeyboard::Space)) cameraSpeed *= 2.f; - // Move agit par défaut dans l'espace local de la caméra, autrement dit la rotation est prise en compte + // 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 (NzKeyboard::IsKeyPressed(NzKeyboard::Up) || NzKeyboard::IsKeyPressed(NzKeyboard::Z)) - camera.Move(NzVector3f::Forward() * cameraSpeed * elapsedTime); + targetPos += camera.GetForward() * cameraSpeed; // Si la flèche du bas ou la touche S est pressée, on recule if (NzKeyboard::IsKeyPressed(NzKeyboard::Down) || NzKeyboard::IsKeyPressed(NzKeyboard::S)) - camera.Move(NzVector3f::Backward() * cameraSpeed * elapsedTime); + targetPos += camera.GetBackward() * cameraSpeed; // Etc... if (NzKeyboard::IsKeyPressed(NzKeyboard::Left) || NzKeyboard::IsKeyPressed(NzKeyboard::Q)) - camera.Move(NzVector3f::Left() * cameraSpeed * elapsedTime); + targetPos += camera.GetLeft() * cameraSpeed; // Etc... if (NzKeyboard::IsKeyPressed(NzKeyboard::Right) || NzKeyboard::IsKeyPressed(NzKeyboard::D)) - camera.Move(NzVector3f::Right() * cameraSpeed * elapsedTime); + targetPos += camera.GetRight() * cameraSpeed; - // Majuscule pour monter, mais dans l'espace global (Sans tenir compte de la rotation) + // Majuscule pour monter, notez l'utilisation d'une direction globale (Non-affectée par la rotation) if (NzKeyboard::IsKeyPressed(NzKeyboard::LShift) || NzKeyboard::IsKeyPressed(NzKeyboard::RShift)) - camera.Move(NzVector3f::Up() * cameraSpeed * elapsedTime, nzCoordSys_Global); + targetPos += NzVector3f::Up() * cameraSpeed; // Contrôle (Gauche ou droite) pour descendre dans l'espace global, etc... if (NzKeyboard::IsKeyPressed(NzKeyboard::LControl) || NzKeyboard::IsKeyPressed(NzKeyboard::RControl)) - camera.Move(NzVector3f::Down() * cameraSpeed * elapsedTime, nzCoordSys_Global); + targetPos += NzVector3f::Down() * cameraSpeed; + + camera.SetPosition((smoothMovement) ? DampedString(camera.GetPosition(), targetPos, elapsedTime) : targetPos, nzCoordSys_Global); // On relance l'horloge updateClock.Restart(); @@ -332,3 +364,36 @@ int main() return EXIT_SUCCESS; } + +NzVector3f DampedString(const NzVector3f& currentPos, const NzVector3f& 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 + NzVector3f displacement = targetPos - currentPos; + + // whats the distance between them? + float displacementLength = displacement.GetLength(); + + // Stops small position fluctuations (integration errors probably - since only using euler) + if (displacementLength < 0.0001f) + 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 = invDisplacementLength * springMagitude * frametime; + displacement *= scalar; + + // move the camera a bit towards the target + return currentPos + displacement; +} diff --git a/include/Nazara/Utility/Mesh.hpp b/include/Nazara/Utility/Mesh.hpp index 6532bc896..aba547d3b 100644 --- a/include/Nazara/Utility/Mesh.hpp +++ b/include/Nazara/Utility/Mesh.hpp @@ -27,6 +27,9 @@ struct NAZARA_API NzMeshParams // Si ceci sera le stockage utilisé par les buffers nzBufferStorage storage = nzBufferStorage_Hardware; + // La mise à l'échelle éventuelle que subira le mesh + NzVector3f scale = NzVector3f::Unit(); + // Charger une version animée du mesh si possible ? bool animated = true; diff --git a/src/Nazara/Graphics/Loaders/OBJ/Loader.cpp b/src/Nazara/Graphics/Loaders/OBJ/Loader.cpp index 7cb189435..0891a57ab 100644 --- a/src/Nazara/Graphics/Loaders/OBJ/Loader.cpp +++ b/src/Nazara/Graphics/Loaders/OBJ/Loader.cpp @@ -123,7 +123,8 @@ namespace NzMeshVertex& vertex = meshVertices[positionIt.second]; const NzVector4f& vec = positions[positionIt.first]; - vertex.position.Set(vec.x/vec.w, vec.y/vec.w, vec.z/vec.w); + vertex.position.Set(vec.x, vec.y, vec.z); + vertex.position *= parameters.mesh.scale/vec.w; int index; diff --git a/src/Nazara/Utility/Loaders/MD2/Loader.cpp b/src/Nazara/Utility/Loaders/MD2/Loader.cpp index ab89431d5..701df3af2 100644 --- a/src/Nazara/Utility/Loaders/MD2/Loader.cpp +++ b/src/Nazara/Utility/Loaders/MD2/Loader.cpp @@ -183,6 +183,10 @@ namespace NzByteSwap(&translate.z, sizeof(float)); #endif + // Un personnage de taille moyenne fait ~50 unités de haut dans Quake 2 + // Avec Nazara, 1 unité = 1 mètre, nous devons donc adapter l'échelle + scale *= parameters.scale/29.f; // 50/29 = 1.72 (Soit 1.72 mètre, proche de la taille moyenne d'un individu) + NzBufferMapper vertexMapper(vertexBuffer.get(), nzBufferAccess_DiscardAndWrite); NzMeshVertex* vertex = reinterpret_cast(vertexMapper.GetPointer()); diff --git a/src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp b/src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp index 3366e7f5c..af5df27c7 100644 --- a/src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp +++ b/src/Nazara/Utility/Loaders/MD5Mesh/Parser.cpp @@ -155,6 +155,10 @@ bool NzMD5MeshParser::Parse(NzMesh* mesh) NzQuaternionf rotationQuat = NzEulerAnglesf(-90.f, 180.f, 0.f); NzString baseDir = m_stream.GetDirectory(); + // Le hellknight de Doom 3 fait ~120 unités, et il est dit qu'il fait trois mètres + // Nous réduisons donc sa taille de 1/40 + NzVector3f scale(m_parameters.scale/40.f); + if (m_parameters.animated) { if (!mesh->CreateSkeletal(m_joints.size())) // Ne devrait jamais échouer @@ -175,8 +179,11 @@ bool NzMD5MeshParser::Parse(NzMesh* mesh) joint->SetName(m_joints[i].name); NzMatrix4f bindMatrix; - bindMatrix.MakeRotation((parent >= 0) ? m_joints[i].bindOrient : rotationQuat * m_joints[i].bindOrient); - bindMatrix.SetTranslation((parent >= 0) ? m_joints[i].bindPos : rotationQuat * m_joints[i].bindPos); // Plus rapide que de multiplier par une matrice de translation + + if (parent >= 0) + bindMatrix.MakeTransform(m_joints[i].bindPos, m_joints[i].bindOrient); + else + bindMatrix.MakeTransform(rotationQuat * m_joints[i].bindPos, rotationQuat * m_joints[i].bindOrient, scale); joint->SetInverseBindMatrix(bindMatrix.InverseAffine()); } @@ -243,7 +250,7 @@ bool NzMD5MeshParser::Parse(NzMesh* mesh) vertexWeight->weights[j] = vertex.startWeight + j; } - bindPosVertex->position = finalPos; + bindPosVertex->position = scale * finalPos; bindPosVertex->uv.Set(vertex.uv.x, 1.f-vertex.uv.y); bindPosVertex++; vertexWeight++; @@ -323,7 +330,7 @@ bool NzMD5MeshParser::Parse(NzMesh* mesh) } // On retourne le modèle dans le bon sens - vertex->position = rotationQuat * finalPos; + vertex->position = scale * (rotationQuat * finalPos); vertex->uv.Set(md5Vertex.uv.x, 1.f - md5Vertex.uv.y); vertex++; } @@ -349,6 +356,7 @@ bool NzMD5MeshParser::Parse(NzMesh* mesh) // Material mesh->SetMaterial(i, baseDir + md5Mesh.shader); + subMesh->GenerateAABB(); subMesh->GenerateNormalsAndTangents(); subMesh->SetMaterialIndex(i); diff --git a/src/Nazara/Utility/Mesh.cpp b/src/Nazara/Utility/Mesh.cpp index f83f50e77..ce412f202 100644 --- a/src/Nazara/Utility/Mesh.cpp +++ b/src/Nazara/Utility/Mesh.cpp @@ -37,6 +37,12 @@ bool NzMeshParams::IsValid() const return false; } + if (scale == NzVector3f::Zero()) + { + NazaraError("Invalid scale"); + return false; + } + return true; }