GLUtils, ou comment faire des modèles 3D facilement


  • Modérateurs

    Bonjour tout le monde! C'est l' heure de "Présente ta création"!

    --Attention : Des parties de ce tutoriel sont obsolètes pour les verions 1.8 et plus (notemment le rendu d'objet avec .obj)--

    Introduction

    Beaucoup de personnes ont demandé à charger des fichiers .obj dans Minecraft.
    Beaucoup ont réussi.
    Mais beaucoup ont échoué.
    Tout ça à cause d'un petit truc insignifiant mais tellement important: le fichier .mtl !
    Ce dernier est crucial, sans lui, la plupart des textures ne s'appliqueront pas comme voulu! Surpris

    Mais alors, comment faire ?

    Avant ce post, vous aviez plusieurs choix:

    1. Chercher pendant des heures comment charger correctement ce .mtl avec Forge sans rien trouver
    2. Utiliser des librairies externes sans trop comprendre ce qu'il s'y passe
    3. Abandonner

    Mais maintenant, plus besoin de se casser la tête!
    GLUtils est là pour vous aider!

    Récupérer GLUtils

    Vous pouvez soit:
    Prendre toutes les sources ici (contient aussi d'autres codes qui peuvent vous intéresser)
    Ou télécharger directement le .jar ici pour les mods Minecraft | ici pour le reste

    Ensuite, ajoutez le code au votre (si vous avez pris les sources) ou ajoutez le .jar dans le classpath (dans le cas du... .jar ^^')

    Transformer un model en objet GLModel

    C'est surement la partie la plus simple:
    ajoutez cette ligne là où vous voulez créer l'instance du model:

    model = new TessellatorModel("/assets/modid/models/ModelDeLaMortQuiTue.obj");
    

    et celle-ci en dehors de toute méthode:

    private TessellatorModel model;
    

    (L'API assume que vous déposez les fichiers de textures et .mtl dans le même fichier)
    Et voilà, vous avez fini cette partie, bravo!

    Dessiner son model

    Il suffit d'appeler la méthode render() sur l'instance de votre model:

    model.render();
    

    Exemple

    Voici un très simple exemple avec notre ami Yoshi qui sera aujourd'hui un bloc de Minecraft (dessiné avec TESR)! (parce que... pourquoi pas ?)
    Bonjour Yoshi!
    Code du rendu:

    package tutorial.glutils;
    
    import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
    import net.minecraft.tileentity.TileEntity;
    
    import org.jglrxavpok.glutils.TessellatorModel;
    import org.lwjgl.opengl.GL11;
    
    public class TileTestObjRenderer extends TileEntitySpecialRenderer
    {
        private TessellatorModel glmodel;
    
        public TileTestObjRenderer()
        {
            glmodel = new TessellatorModel("/assets/obj/Yoshi.obj");
        }
    
        @Override
        public void renderTileEntityAt(TileEntity tileentity, double d0, double d1, double d2, float f)
        {
            GL11.glPushMatrix();
            GL11.glTranslated(d0+0.5, d1, d2+0.5);
            GL11.glScaled(0.01, 0.01, 0.01);
            glmodel.render();
            GL11.glPopMatrix();
        }
    }
    

    Résultat:

    Et voilà, c'est tout ce qu'il y a à faire Grand sourire

    Bonus

    Si vous avez regardé le résultat juste au dessus, vous avez surement remarqué que l'on voit très facilement les différentes faces du model.
    Pour s'en débarrasser, ajoutez juste AVANT d'appeler la méthode render() du model:

    GL11.glShadeModel(GL11.GL_SMOOTH);
    

    Je vous conseille aussi d'appeler

    model.regenerateNormals();
    

    Après avoir créer le model afin de corriger les possibles problèmes dans le fichier du model et avoir une réflexion de la lumière correcte.

    Code de Yoshi corrigé:

    package tutorial.glutils;
    
    import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
    import net.minecraft.tileentity.TileEntity;
    
    import org.jglrxavpok.glutils.TessellatorModel;
    import org.lwjgl.opengl.GL11;
    
    
    public class TileTestObjRenderer extends TileEntitySpecialRenderer
    {
        private TessellatorModel glmodel;
    
        public TileTestObjRenderer()
        {
            glmodel = new TessellatorModel("/assets/obj/Yoshi.obj");
            glmodel.regenerateNormals();
        }
    
        @Override
        public void renderTileEntityAt(TileEntity tileentity, double d0, double d1, double d2, float f)
        {
            GL11.glPushMatrix();
            GL11.glTranslated(d0+0.5, d1, d2+0.5);
            GL11.glScaled(0.01, 0.01, 0.01);
            GL11.glShadeModel(GL11.GL_SMOOTH);
            glmodel.render();
            GL11.glPopMatrix();
        }
    }
    

    Cela vous donnera un rendu beaucoup plus lisse:
    modèle de Yoshi dans Minecraft

    Vidéo bonus :

    Youtube Video

    Animer un modèle

    Pour cette partie, nous prendons la Vorpal Blade du jeu Alice: Madness Returns modifié par mes soins et nous ferons basculer la lame d'avant en arrière.
    Parce que je suis sympa, je vous donne le code pour l'afficher en tant que modèle fixe:

    package fr.mff.tuto;
    
    import cpw.mods.fml.common.eventhandler.SubscribeEvent;
    import net.minecraft.entity.Entity;
    import net.minecraft.item.ItemStack;
    import net.minecraftforge.client.IItemRenderer;
    
    import org.jglrxavpok.glutils.TessellatorModel;
    import org.jglrxavpok.glutils.TessellatorModelEvent;
    import org.jglrxavpok.glutils.TessellatorModelEvent.RenderGroupEvent;
    import org.lwjgl.opengl.GL11;
    
    public class ItemKnikeRenderer implements IItemRenderer
    {
        private TessellatorModel model;
        private double bladeAngle;
        private int direction;
    
        public ItemKnikeRenderer()
        {
            model = new TessellatorModel("/assets/obj/VorpalBlade.obj");
            model.regenerateNormals();
        }
       
        @Override
        public boolean handleRenderType(ItemStack item, ItemRenderType type)
        {
            switch(type)
            {
                case ENTITY:
                return true;
                case EQUIPPED_FIRST_PERSON:
                return true;
                case EQUIPPED:
                return true;
                case INVENTORY:
                return true;
                default:
                return false;
            }
        }
    
        @Override
        public boolean shouldUseRenderHelper(ItemRenderType type, ItemStack item, ItemRendererHelper helper)
        {
            if(type == ItemRenderType.INVENTORY && helper == ItemRendererHelper.INVENTORY_BLOCK)
                return true;
            return false;
        }
    
        @Override
        public void renderItem(ItemRenderType type, ItemStack item, Object... data)
        {
            GL11.glScaled(0.05,0.05,0.05);
            switch(type)
            {
                case ENTITY:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(0,0.1,0);
                    GL11.glRotated(((Entity)data[1]).rotationYaw+=0.1, 0, 1, 0);
                    GL11.glRotated(10, 0, 0, 1);
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case EQUIPPED_FIRST_PERSON:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10,10.0,0.0);
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(-10, 1, 0, 0);
    
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(20, 1, 0, 0);
                    GL11.glRotated(-10, 0, 0, 1);
                    GL11.glRotated(25, 0, 1, 0);
                    GL11.glRotated(10, 1, 0, 0);
    
                    GL11.glRotated(180, 0, 1, 0);
    
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case EQUIPPED:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10,0.0,0.0);
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(-10, 1, 0, 0);
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case INVENTORY:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10, -10, 0);
                    GL11.glRotated(90, 0, 1, 0);
                    GL11.glRotated(-45, 1, 0, 0);
                    GL11.glRotated(180, 0, 1, 0);
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glDisable(GL11.GL_DEPTH_TEST);
                    GL11.glPopMatrix();
                    break;
                }
            }
        }
    }
    

    Premièrement, vous devez savoir que les animations marchent à partir d'events Forge ! (yeah) Donc vous devez récupérer les events (nous utiliserons la classe de render pour ce tutoriel mais vous pouvez très bien utiliser une classe annexe).
    Pour enregistrer votre classe et intercepter les events, ajoutez ceci dans votre classe:

    TessellatorModel.MODEL_RENDERING_BUS.register(this);
    

    Et

    model.setID("votreIDDeModel");
    

    Cet identifiant vous permet de savoir quel modèle est affiché.

    Fil rouge:

    package fr.mff.tuto;
    
    import cpw.mods.fml.common.eventhandler.SubscribeEvent;
    import net.minecraft.entity.Entity;
    import net.minecraft.item.ItemStack;
    import net.minecraftforge.client.IItemRenderer;
    
    import org.jglrxavpok.glutils.TessellatorModel;
    import org.jglrxavpok.glutils.TessellatorModelEvent;
    import org.jglrxavpok.glutils.TessellatorModelEvent.RenderGroupEvent;
    import org.lwjgl.opengl.GL11;
    
    public class ItemKnikeRenderer implements IItemRenderer
    {
    
        private TessellatorModel model;
        private double bladeAngle;
        private int direction;
    
        public ItemKnikeRenderer()
        {
            model = new TessellatorModel("/assets/obj/VorpalBlade.obj");
            model.regenerateNormals();
            model.setID("blade");
            TessellatorModel.MODEL_RENDERING_BUS.register(this);
        }
        
        @Override
        public boolean handleRenderType(ItemStack item, ItemRenderType type)
        {
            switch(type)
            {
                case ENTITY:
                return true;
                case EQUIPPED_FIRST_PERSON:
                return true;
                case EQUIPPED:
                return true;
                case INVENTORY:
                return true;
                default:
                return false;
            }
        }
    
        @Override
        public boolean shouldUseRenderHelper(ItemRenderType type, ItemStack item, ItemRendererHelper helper)
        {
            if(type == ItemRenderType.INVENTORY && helper == ItemRendererHelper.INVENTORY_BLOCK)
                return true;
            return false;
        }
    
        @Override
        public void renderItem(ItemRenderType type, ItemStack item, Object... data)
        {
            GL11.glScaled(0.05,0.05,0.05);
            switch(type)
            {
                case ENTITY:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(0,0.1,0);
                    GL11.glRotated(((Entity)data[1]).rotationYaw+=0.1, 0, 1, 0);
                    GL11.glRotated(10, 0, 0, 1);
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case EQUIPPED_FIRST_PERSON:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10,10.0,0.0);
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(-10, 1, 0, 0);
    
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(20, 1, 0, 0);
                    GL11.glRotated(-10, 0, 0, 1);
                    GL11.glRotated(25, 0, 1, 0);
                    GL11.glRotated(10, 1, 0, 0);
    
                    GL11.glRotated(180, 0, 1, 0);
    
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case EQUIPPED:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10,0.0,0.0);
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(-10, 1, 0, 0);
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case INVENTORY:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10, -10, 0);
                    GL11.glRotated(90, 0, 1, 0);
                    GL11.glRotated(-45, 1, 0, 0);
                    GL11.glRotated(180, 0, 1, 0);
                                GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glDisable(GL11.GL_DEPTH_TEST);
                    GL11.glPopMatrix();
                    break;
                }
            }
        }
    }
    

    Il y a différents events:

    • TessellatorModelEvent.RenderPre : Appelé avant d'afficher le modèle.
    • TessellatorModelEvent.RenderGroupEvent.Pre : Appelé avant d'afficher un groupe de faces
    • TessellatorModelEvent.RenderGroupEvent.Post : Appelé après avoir afficher un groupe de faces

    Pour notre exemple, nous voulons balancer la lame donc avant d'afficher le modèle, nous gérons les angles:

        @SubscribeEvent
        public void onPreRenderModel(TessellatorModelEvent.RenderPre event)
        {
            if(event.model.getID() != null && event.model.getID().equals("blade"))
                bladeAngle+=direction;
           
            if(bladeAngle > 65)
            {
                direction = -2;
            }
            if(bladeAngle < -65)
            {
               direction = 2;
            }
            if(direction == 0)
               direction = 2;
       }
    

    Ainsi, la lame se balancera entre 65° et -65°.

    Ensuite, il faut appliquer la rotation:

    @SubscribeEvent
       public void onPreRenderGroup(RenderGroupEvent.Pre event)
       {
           if(event.model.getID() != null)
           {
               if(event.model.getID().equals("blade"))
               {
                   if(event.group.equals("Blade"))
                   {
                       GL11.glPushMatrix();
                       double x = 0;
                       double y = 6;
                       double z = 0;
                       GL11.glTranslated(x, y, z);
                       GL11.glRotated(bladeAngle, 1, 0, 0);
                       GL11.glTranslated(-x, -y, -z);
                   }
               }
           }
       }
    
    if(event.model.getID().equals("blade"))
    {
        if(event.group.equals("Blade"))
        {
    

    Nous vérifions que le groupe à dessiner est la lame du couteau.

    GL11.glPushMatrix();
    double x = 0;
    double y = 6;
    double z = 0;
    GL11.glTranslated(x, y, z);
    GL11.glRotated(bladeAngle, 1, 0, 0);
    GL11.glTranslated(-x, -y, -z);
    

    GL11.glPushMatrix(); on sauvegarde l'état d'OpenGL.
    GL11.glRotated Application de la rotation.
    GL11.glTranslated(x, y, z); et GL11.glTranslated(-x, -y, -z); permet de définir un point de rotation. (par défaut (0;0;0) )

    Puisque l'on a sauvegardé l'état d'OpenGL, il faut le rétablir après avoir dessiné le groupe:

    @SubscribeEvent
       public void onPostRenderGroup(RenderGroupEvent.Post event)
       {
           if(event.model.getID() != null)
           {
               if(event.model.getID().equals("blade"))
               {
                   if(event.group.equals("Blade"))
                   {
                       GL11.glPopMatrix();
                   }
               }
    
           }
       }
    

    Exemple complet :

    package fr.mff.tuto;
    
    import cpw.mods.fml.common.eventhandler.SubscribeEvent;
    import net.minecraft.entity.Entity;
    import net.minecraft.item.ItemStack;
    import net.minecraftforge.client.IItemRenderer;
    
    import org.jglrxavpok.glutils.TessellatorModel;
    import org.jglrxavpok.glutils.TessellatorModelEvent;
    import org.jglrxavpok.glutils.TessellatorModelEvent.RenderGroupEvent;
    import org.lwjgl.opengl.GL11;
    
    public class ItemKnikeRenderer implements IItemRenderer
    {
    
        private TessellatorModel model;
        private double bladeAngle;
        private int direction;
    
        public ItemKnikeRenderer()
        {
            model = new TessellatorModel("/assets/obj/VorpalBlade.obj");
            model.regenerateNormals();
            model.setID("blade");
            TessellatorModel.MODEL_RENDERING_BUS.register(this);
        }
       
        @SubscribeEvent
       public void onPreRenderModel(TessellatorModelEvent.RenderPre event)
       {
           if(event.model.getID() != null && event.model.getID().equals("blade"))
               bladeAngle+=direction;
           
           if(bladeAngle > 65)
           {
               direction = -2;
           }
           if(bladeAngle < -65)
           {
               direction = 2;
           }
           if(direction == 0)
               direction = 2;
       }
    
        @SubscribeEvent
        public void onPreRenderGroup(RenderGroupEvent.Pre event)
        {
            if(event.model.getID() != null)
            {
                if(event.model.getID().equals("blade"))
                {
                    if(event.group.equals("Blade"))
                    {
                        GL11.glPushMatrix();
                        double x = 0;
                        double y = 6;
                        double z = 0;
                        GL11.glTranslated(x, y, z);
                        GL11.glRotated(bladeAngle, 1, 0, 0);
                        GL11.glTranslated(-x, -y, -z);
                    }
                }
            }
        }
       
       @SubscribeEvent
       public void onPostRenderGroup(RenderGroupEvent.Post event)
       {
           if(event.model.getID() != null)
           {
               if(event.model.getID().equals("blade"))
               {
                   if(event.group.equals("Blade"))
                   {
                       GL11.glPopMatrix();
                   }
               }
    
           }
       }
    
    
        @Override
        public boolean handleRenderType(ItemStack item, ItemRenderType type)
        {
            switch(type)
            {
                case ENTITY:
                return true;
                case EQUIPPED_FIRST_PERSON:
                return true;
                case EQUIPPED:
                return true;
                case INVENTORY:
                return true;
                default:
                return false;
            }
        }
    
        @Override
        public boolean shouldUseRenderHelper(ItemRenderType type, ItemStack item, ItemRendererHelper helper)
        {
            if(type == ItemRenderType.INVENTORY && helper == ItemRendererHelper.INVENTORY_BLOCK)
                return true;
            return false;
        }
    
        @Override
        public void renderItem(ItemRenderType type, ItemStack item, Object... data)
        {
            GL11.glScaled(0.05,0.05,0.05);
            switch(type)
            {
                case ENTITY:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(0,0.1,0);
                    GL11.glRotated(((Entity)data[1]).worldObj.getB.rotationYaw+=0.1, 0, 1, 0);
                    GL11.glRotated(10, 0, 0, 1);
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case EQUIPPED_FIRST_PERSON:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10,10.0,0.0);
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(-10, 1, 0, 0);
    
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(20, 1, 0, 0);
                    GL11.glRotated(-10, 0, 0, 1);
                    GL11.glRotated(25, 0, 1, 0);
                    GL11.glRotated(10, 1, 0, 0);
    
                    GL11.glRotated(180, 0, 1, 0);
    
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case EQUIPPED:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10,0.0,0.0);
                    GL11.glRotated(200, 0, 1, 0);
                    GL11.glRotated(-10, 1, 0, 0);
                    GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glPopMatrix();
                    break;
                }
                case INVENTORY:
                {
                    GL11.glPushMatrix();
                    GL11.glTranslated(10, -10, 0);
                    GL11.glRotated(90, 0, 1, 0);
                    GL11.glRotated(-45, 1, 0, 0);
                    GL11.glRotated(180, 0, 1, 0);
                                GL11.glShadeModel(GL11.GL_SMOOTH);
                    model.render();
                    GL11.glDisable(GL11.GL_DEPTH_TEST);
                    GL11.glPopMatrix();
                    break;
                }
            }
        }
    }
    

    N'hésitez pas à poser des questions!

    Voilà! Ce tutoriel est maintenant fini!



  • @'jglrxavpok':

    Avant ce post, vous aviez plusieurs choix:
    Chercher pendant des heures comment charger correctement ce .mtl avec Forge sans rien trouver
    Utiliser des librairies externes sans trop comprendre ce qu'il s'y passe
    Abandonner

    Super résumé de mon post, merci Clin d'oeil

    J'arrivais nickel jusqu’au fichier .mtl, et là, aucun moyen d'appliquer du multitexturing, j’étais désespéré, et c'est la qu'est arrivé Super jglrxavpok et son tuto qui m'a sauvé la vie Sourire

    Jte montrerai mes créations

    Continue comme ça et merci Clin d'oeil


  • Modérateurs

    Merci 🙂

    J'ai hâte de voir ce que tu va en faire 😄



  • Je cherche quelque chose d'impressionnant

    Ps: Maintenant si tu arrives à faire un tutoriel pour ajouter des objets animés, je t’appellerais maître toute ma vie

    😄


  • Modérateurs



  • Possible d'avoir les sources du tuto ?

    Parsque j'ai une ptit erreur:

    java.lang.ArrayIndexOutOfBoundsException: -1


  • Modérateurs

    Fais voir ton code et le log



  • Je sais pourquoi, il y a un nombre de face limité pour le chargement

    Adieu gros models à charger 😞


  • Modérateurs

    Je vais regarder ça ;)___
    Je ne crois pas qu'il y est une limite 😕

    J'arrive à charger ce gros modèle…

    Envoie moi tes fichiers pour le modèle en MP


  • Modérateurs

    Avec maintenant une vidéo pour montrer les animations 😄

    Edit: Tutoriel modifié pour utiliser TessellatorModel.
    Partie sur les animations bientôt! 😄



  • [lien d'image mort ..]

    Un petit exemple bien simpa aussi !



  • Comment faire avec un mob ? Pour appliquer ce genre de model.obj à un mob ? Est-ce lourd pour minecraft ?


  • Modérateurs

    @'ZeAmateis':

    Comment faire avec un mob ? Pour appliquer ce genre de model.obj à un mob ? Est-ce lourd pour minecraft ?

    Pour un mob, utilise un render et appelle la méthode render() du model dans renderEntity() du render.
    (Pour les renders, une petite partie est expliquée ici)
    Pour le poids, tout dépend du model.



  • Très bien j'irais tester tout ça ! Mais j'ai un problème… Mon Model est blanc est-ce normal ?


    Edit: Oublié le .mtl ><


    Edit: 2 Une autre question niveau animations... Comment faire ? Est-ce possible ? Toujours pareil, est-ce lourd pour Minecraft ? Un tuto là dessus serait sympa de ta part jglrxavpok ^^


  • Modérateurs

    Je suis toujours en train de travailler là-dessus.
    Peut-être un tuto dans 1 semaine ? 😄



  • @'jglrxavpok':

    Je suis toujours en train de travailler là-dessus.
    Peut-être un tuto dans 1 semaine ? 😄

    Ah ! ça c'est cool ! Ça me sera utile pour mon projet !


  • Administrateurs

    L'api ne fonctionne pas en 1.7… ^^

    Caused by: java.lang.NoClassDefFoundError: net/minecraftforge/event/Event


  • Modérateurs

    Elias, tu as du télécharger les mauvaises sources 😕
    En 1.7 la classe Event n'est plus la même https://github.com/jglrxavpok/MCGLUtils/commit/a2647cfdb3b08319e7c1955aa993e29f71324608



  • C'est génial! j'attend avec impatience la partie sur l'animation ^^
    Elles est tj d'actualité? Et elle sera pour quelle version: 1.7 ou 1.6?



  • Gargan, étant donné que cette lib est une lib LWJGL et non Minecraft, elle devrait marcher tant que Minecraft utilise LWJGL.

    Soit : pour toujours