#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; // Cette ligne active le mode de compatibilité d'OpenGL lors de l'initialisation de Nazara (Nécessaire pour le shader) NzContextParameters::defaultCompatibilityProfile = true; // 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 renderer; if (!renderer) { // Ç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é. NzShader shader; if (!shader.Create(nzShaderLanguage_GLSL)) { std::cout << "Failed to load shader" << std::endl; std::getchar(); return EXIT_FAILURE; } // Une fois le shader créé, nous devons lui spécifier les codes sources de nos shaders // Pour notre exemple nous prendrons un shader très simple // Un shader doit obligatoirement posséder au moins deux codes, un pour le fragment shader et un pour le vertex shader // Le fragment shader traite la couleur de nos pixels if (!shader.LoadFromFile(nzShaderType_Fragment, "shaders/basic.frag")) { std::cout << "Failed to load fragment shader from file" << std::endl; // À la différence des autres ressources, le shader possède un log qui peut indiquer les erreurs en cas d'échec std::cout << "Log: " << shader.GetLog() << std::endl; std::getchar(); return EXIT_FAILURE; } // Le vertex shader (Transformation des vertices de l'espace 3D vers l'espace écran) if (!shader.LoadFromFile(nzShaderType_Vertex, "shaders/basic.vert")) { std::cout << "Failed to load vertex shader from file" << std::endl; std::cout << "Log: " << shader.GetLog() << std::endl; std::getchar(); return EXIT_FAILURE; } // Une fois les codes sources de notre shader chargé, nous pouvons le compiler, afin de le rendre utilisable if (!shader.Compile()) { std::cout << "Failed to compile shader" << std::endl; std::cout << "Log: " << shader.GetLog() << std::endl; std::getchar(); return EXIT_FAILURE; } // 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)) { 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 camRot = camera.GetDerivedRotation().ToEulerAngles(); thirdPerson = false; } else 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: { 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); } if (thirdPerson) { static NzQuaternionf rotDown(NzEulerAnglesf(-35.f, 0.f, 0.f)); // Une rotation pour regarder vers le bas camera.SetRotation(drfreak.GetDerivedRotation() * rotDown); camera.SetTranslation(drfreak.GetDerivedTranslation() + camera.GetDerivedRotation() * NzVector3f(0.f, 30.f, 50.f)); } // 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); 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()); 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(); } } NzDebugDrawer::Uninitialize(); 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; } texture->SetAnisotropyLevel(NzRenderer::GetMaxAnisotropyLevel()); // Un filtrage anisotropique pour la texture texture->SetWrapMode(nzTextureWrap_Repeat); // Si les coordonnées de texture dépassent 1.f, la texture sera répétée 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); NzRenderer::ApplyMaterial(model.GetMaterial(i)); 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()); } }