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:
- 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
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:
Vidéo bonus :
https://www.youtube.com/watch?v=zWXUrGBsg4Y
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!