Jump to content

[1.14.4][RESOLVED] OBJ Models: How to Get Started


TheMikeste1

Recommended Posts

I'm trying to learn how to use OBJ models for my mod, but I'm having some difficulty getting started. I've been trying to find some tutorials or documentation on how to do import them and use them, but what I've found is out of date, and some of the functions are removed/renamed. I've looked at a few tutorials, including the 1.13 model section of the Forge Docs website (uses IStateMapper, which no longer exists), as well as this Forge forum tutorial (outdated, and many of the functions used no longer exist). I also peeked at ModelLoader, but the function #setCustomModelResourceLocation (which a lot of what I've found use) commented out, so I'm assuming there's a new way to do it.

Would anyone be able to point me towards current tutorials, documentation, or code examples so I can figure out how to use OBJ models? Or possibly outline it for me here? I'm specifically looking for info on how to do it for a Block, but help with Items or Entities would also be useful.

Many thanks!

Link to comment
Share on other sites

Wow, is it really that simple now? That seems a lot easier than previous versions.

I seem to still be doing something wrong. I'm getting "[minecraft/ModelBakery]: Unable to load model: 'wabbits:block/test_block.obj' referenced from: wabbits:test_block#: java.io.FileNotFoundException: wabbits:models/block/test_block.obj.json" when launching, and the block is the standard pink and black "no-texture-found" block. It looks like ".json" is being appended to my file name? Perhaps I'm just doing something wrong? Do I need to do anything with ModelBakery?

Here's my blockstate test_block.json:

{
  "variants": {
    "": { "model": "wabbits:test_block.obj" }
  }
}


And my ClientProxy:

public class ClientProxy implements IProxy {

    static {
        OBJLoader.INSTANCE.addDomain(Constants.MOD_ID); //MOD_ID == "wabbits"
    }

    @Override
    public void init() {

    }

    @Override
    public World getClientWorld() {
        return Minecraft.getInstance().world;
    }

    @Override
    public PlayerEntity getClientPlayer() {
        return Minecraft.getInstance().player;
    }
}



EDIT: I noticed poking through the Forge GitHub that the .obj file paths don't seem to need the folder in front of it, so I've changed my blockstate .json to have 

 "": { "model": "wabbits:test_block.obj" }

instead of 

 "": { "model": "wabbits:blocks/test_block.obj" }

 

Edited by TheMikeste1
Blockstate JSON update
Link to comment
Share on other sites

After poking around a bit more, I found the ModelRegistryEvent. I assume I'm supposed to register my models here? This is what I have right now:

@Mod.EventBusSubscriber(modid = Constants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Models {
    @SubscribeEvent
    public static void registerModels(ModelRegistryEvent event) {
        ModelLoader.addSpecialModel(ResourceLocation.tryCreate("wabbits:models/block/test_block.obj"));

        OBJModel model;
        try {
            model = (OBJModel) OBJLoader.INSTANCE.loadModel(ResourceLocation.tryCreate("wabbits:models/block/test_block.obj"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

I'm using #addSpecialModel since #setCustomModelResourceLocation is commented out. Looking at them, they don't seem to be do the same thing so I doubt that's the solution.

I also noticed that OBJLoader has #loadModel, so I attempted to load in the model. Unfortunately, with the way I'm currently set up, I get a NullPointerException from manager being null on OBJLoader line 85. I don't think I can set manager to anything (it's private), so is there a different time I do this?
 

Link to comment
Share on other sites

So I've made some substantial progress! I eventually found an OBJ test on the MC Forge Github (modelscode), and my model now renders in-game! Unfortunately, the model is totally black (it's supposed to be bright yellow). If anyone has any insight as to why that may be, I'd appreciate it.

Attached are my model files in case those would be useful..

test_block.mtl test_block.obj

Edited by TheMikeste1
  • Thanks 1
Link to comment
Share on other sites

11 hours ago, ZigTheHedge said:

Try flipping "V" in your blockstate


"custom": { "flip-v": true }

 

Thanks for your suggestion, but unfortunately it didn't help. I'm now trying to use "map_Kd" to get the texture, but that doesn't seem to work, either. It changes to the "no texture found" texture. If I remove map_Kd, it goes back to black.

I'm going to keep trying to figure it out. I've been searching through parts of the Forge Github, but there's a lot to go through. In the mean time, if anyone is willing to give me a hand, here's my repository (model_test branch):

TestBlock Class
Model Registering

test_block Blockstate
test_block.obj

test_block.mtl

 

And an image of what the block currently looks like (with map_Kd):
2019-08-30.png.ac39a6d0905bb3b7259cd325c8860492.png

Edited by TheMikeste1
Added image
Link to comment
Share on other sites

8 hours ago, DefectiveProgram said:

Try removing ".png" from the texture path in your .mtl maybe?

Unfortunately that doesn't work either. I've tried several different paths with and without the .png. I'm probably missing something super obvious that I just can't figure out. 

Link to comment
Share on other sites

8 minutes ago, DefectiveProgram said:

Try using a vanilla texture (Also without ".png" as that appears to be automatically appended.) I'm guessing the texture isn't being loaded.

2019-08-31.png.118cafed9723fc8a6c7c24dc9a88ca5e.png

Huh, that works. I guess I should have thought of that. So with OBJ models do I need to tell it to load in my texture? I had assumed it would load them in like it does with other blocks. I notice there's a lot of z-fighting though. Also, why is it so dark?

Link to comment
Share on other sites

  • TheMikeste1 changed the title to [1.14.4][RESOLVED] OBJ Models: How to Get Started
  • 4 weeks later...
  • 3 weeks later...
On 9/1/2019 at 2:36 AM, TheMikeste1 said:

That did it! Thank you so much!

Can you please post the source code?

 

Nvm. Sorry.

 

I keep getting the error that you got:

 
 
 
 
On 8/29/2019 at 10:41 PM, TheMikeste1 said:

I seem to still be doing something wrong. I'm getting "[minecraft/ModelBakery]: Unable to load model: 'wabbits:block/test_block.obj' referenced from: wabbits:test_block#: java.io.FileNotFoundException: wabbits:models/block/test_block.obj.json" when launching, and the block is the standard pink and black "no-texture-found" block. It looks like ".json" is being appended to my file name? Perhaps I'm just doing something wrong? Do I need to do anything with ModelBakery?

Any solutions?

Edited by jun2040
Link to comment
Share on other sites

I'm getting these exceptions. I get the black and purple texture in the game.

[15:34:09] [Server-Worker-3/WARN] [minecraft/ModelBakery]: Exception loading blockstate definition: unixmod:blockstates/fabricator.json: java.io.FileNotFoundException: unixmod:blockstates/fabricator.json
[15:34:09] [Server-Worker-3/WARN] [minecraft/ModelBakery]: Exception loading blockstate definition: 'unixmod:blockstates/fabricator.json' missing model for variant: 'unixmod:fabricator#'
[15:34:10] [Server-Worker-3/WARN] [minecraft/ModelBakery]: Unable to load model: 'unixmod:fabricator#inventory' referenced from: unixmod:fabricator#inventory: java.io.FileNotFoundException: unixmod:models/item/fabricator.json

Here's my blockstates:

{
    "variants": {
        "facing=north": { "model": "unixmod:fabricator.obj" },
        "facing=south": { "model": "unixmod:fabricator.obj", "y": 180 },
        "facing=west": { "model": "unixmod:fabricator.obj", "y": 270 },
        "facing=east": { "model": "unixmod:fabricator.obj", "y": 90 },
        "facing=up": { "model": "unixmod:fabricator.obj", "x": -90 },
        "facing=down": { "model": "unixmod:fabricator.obj", "x": 90 },
    }
}

obj:

mtllib block.mtl
o Cube
v 8.000000 8.000000 -8.000000
v 8.000000 -8.000000 -8.000000
v 8.000000 8.000000 8.000000
v 8.000000 -8.000000 8.000000
v -8.000000 8.000000 -8.000000
v -8.000000 -8.000000 -8.000000
v -8.000000 8.000000 8.000000
v -8.000000 -8.000000 8.000000
l 6 8
l 2 6
l 1 2
l 8 7
l 3 4
l 5 6
l 3 7
l 1 3
l 8 4
l 7 5
l 5 1
l 4 2
o Cube.001
v 5.000000 5.000000 -5.000000
v 5.000000 -5.000000 -5.000000
v 5.000000 5.000000 5.000000
v 5.000000 -5.000000 5.000000
v -5.000000 5.000000 -5.000000
v -5.000000 -5.000000 -5.000000
v -5.000000 5.000000 5.000000
v -5.000000 -5.000000 5.000000
vt 0.286909 0.500000
vt 0.073817 0.286909
vt 0.286909 0.286909
vt 0.500000 0.286909
vt 0.500000 0.500000
vt 0.286909 0.713091
vt 0.713091 0.500000
vt 0.286909 0.926183
vt 0.500000 0.713091
vt 0.500000 0.926183
vt 0.500000 0.073817
vt 0.286909 0.073817
vt 0.073817 0.500000
vt 0.713091 0.286909
vn -1.0000 0.0000 0.0000
vn 0.0000 -1.0000 0.0000
vn 0.0000 0.0000 1.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 -1.0000
usemtl Material
s off
f 16/1/1 13/2/1 14/3/1
f 10/4/2 16/1/2 14/3/2
f 12/5/3 15/6/3 16/1/3
f 10/4/4 11/7/4 12/5/4
f 13/8/5 11/9/5 9/10/5
f 9/11/6 14/3/6 13/12/6
f 16/1/1 15/13/1 13/2/1
f 10/4/2 12/5/2 16/1/2
f 12/5/3 11/9/3 15/6/3
f 10/4/4 9/14/4 11/7/4
f 13/8/5 15/6/5 11/9/5
f 9/11/6 10/4/6 14/3/6

 

mtl:

newmtl Material
Ns 323.999994
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.000000 0.005390
Ks 0.500000 0.500000 0.500000
Ke 0.0 0.0 0.0
Ni 1.450000
d 1.000000
illum 2

 

Fabricator:

package com.jun2040.unixmod.blocks;

import net.minecraft.block.Block;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.IBlockReader;

public class Fabricator extends Block {

    public Fabricator() {
        super(Properties.create(Material.IRON)
                .sound(SoundType.METAL)
                .hardnessAndResistance(2.0f)
        );
        setRegistryName("fabricator");
    }

    @Override
    public BlockRenderType getRenderType(BlockState state) {
        return BlockRenderType.MODEL;
    }

}

 

ClientProxy:

package com.jun2040.unixmod.setup;

import com.jun2040.unixmod.UnixMod;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.world.World;
import net.minecraftforge.client.model.obj.OBJLoader;

public class ClientProxy implements IProxy {

    static {
        OBJLoader.INSTANCE.addDomain(UnixMod.MODID);
    }

    @Override
    public void init() {

    }

    @Override
    public World getClientWorld() {
        return Minecraft.getInstance().world;
    }

    @Override
    public PlayerEntity getClientPlayer() {
        return Minecraft.getInstance().player;
    }
}

Main mod class:

package com.jun2040.unixmod;

import com.jun2040.unixmod.blocks.Fabricator;
import com.jun2040.unixmod.blocks.ModBlocks;
import com.jun2040.unixmod.setup.ClientProxy;
import com.jun2040.unixmod.setup.IProxy;
import com.jun2040.unixmod.setup.ModSetup;
import com.jun2040.unixmod.setup.ServerProxy;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.BasicState;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.client.model.obj.OBJLoader;
import net.minecraftforge.client.model.obj.OBJModel;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.InterModComms;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent;
import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.stream.Collectors;

@Mod("unixmod")
public class UnixMod {

    public static final String MODID = "sourcemod";

    public static IProxy proxy = DistExecutor.runForDist(() -> () -> new ClientProxy(), () -> () -> new ServerProxy());

    public static ModSetup setup = new ModSetup();

    private static final Logger LOGGER = LogManager.getLogger();


    public UnixMod() {
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::enqueueIMC);
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::processIMC);
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::doClientStuff);

        MinecraftForge.EVENT_BUS.register(this);
    }


    private void setup(final FMLCommonSetupEvent event) {
    }


    private void doClientStuff(final FMLClientSetupEvent event) {
        OBJLoader.INSTANCE.addDomain(UnixMod.MODID);
    }


    private void enqueueIMC(final InterModEnqueueEvent event) {
    }


    private void processIMC(final InterModProcessEvent event) {
    }


    @SubscribeEvent
    public void onServerStarting(FMLServerStartingEvent event) {
    }

    @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
    public static class RegistryEvents {

        @SubscribeEvent
        public static void onBlocksRegistry(final RegistryEvent.Register<Block> event) {
            event.getRegistry().register(new Fabricator());
        }

        @SubscribeEvent
        public static void onItemsRegistry(final RegistryEvent.Register<Item> event) {
            event.getRegistry().register(new BlockItem(ModBlocks.FABRICATOR, new Item.Properties()).setRegistryName("fabricator"));
        }

        @SubscribeEvent
        public static void onModelBakeEvent(ModelBakeEvent event) {
            try {
                IUnbakedModel model = ModelLoaderRegistry.getModelOrMissing(new ResourceLocation("unixmod:fabricator.obj"));

                if (model instanceof OBJModel) {
                    IBakedModel bakedModel = model.bake(event.getModelLoader(), ModelLoader.defaultTextureGetter(), new BasicState(model.getDefaultState(), false), DefaultVertexFormats.ITEM);
                    event.getModelRegistry().put(new ModelResourceLocation("stick", "inventory"), bakedModel);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
Link to comment
Share on other sites

@TheMikeste1 I created the model.json for my block and the block doesn't seem to render the model for some reason. I also got some problems with the item.

here's my github: https://github.com/jun2040/unixmod

Edit: I commented out the model registries thing... The model works now but the item's not working and the model's texture is too dark.

Edited by jun2040
Link to comment
Share on other sites

1 hour ago, blinky000 said:

jun2040 you commented out the onModelBakeEvent() ? and it worked?  all my messing around has got me an empty cube

Um. I meant I commented that out and fixed that. I uncommented it. But the textures are still dark and there's this weird thing going on with my block and things beside the block. Do you know how to register multiple models on onModelBakeEvent()?

 

Edited by jun2040
Link to comment
Share on other sites

This thread was made before Forge blockstates were fixed for 1.14.4. With their introduction, manually stitching the texture and loading the block model is no longer necessary as those were workarounds. Item blocks (probably items as well) still need to use the ModelBakeEvent though. For item blocks it would seem you can just grab the model from the model registry, as it appears to be loaded before the event fires. Then pass it through a PerspectiveMapWrapper to apply the perspective transforms. And just put the PerspectiveMapWrapper into the model registry with the inventory variant.

 

And here are the item block transforms to pass into the PerspectiveMapWrapper:

private static final TRSRTransformation THIRD_PERSON_BLOCK = Transforms.convert(0, 2.5f, 0, 75, 45, 0, 0.375f);
private static final ImmutableMap<TransformType, TRSRTransformation> BLOCK_TRANSFORMS = ImmutableMap.<TransformType, TRSRTransformation>builder()
.put(TransformType.GUI, Transforms.convert(0, 0, 0, 30, 225, 0, 0.625f))
.put(TransformType.GROUND, Transforms.convert(0, 3, 0, 0, 0, 0, 0.25f)).put(TransformType.FIXED, Transforms.convert(0, 0, 0, 0, 0, 0, 0.5f))
.put(TransformType.THIRD_PERSON_RIGHT_HAND, THIRD_PERSON_BLOCK)
.put(TransformType.THIRD_PERSON_LEFT_HAND, Transforms.leftify(THIRD_PERSON_BLOCK))
.put(TransformType.FIRST_PERSON_RIGHT_HAND, Transforms.convert(0, 0, 0, 0, 45, 0, 0.4f))
.put(TransformType.FIRST_PERSON_LEFT_HAND, Transforms.convert(0, 0, 0, 0, 225, 0, 0.4f))
.build();

 

2 hours ago, blinky000 said:

now im worried that the texture HAS to me hard coded in the mtl . previous versions you could define the texture in the blockstate

Yeah, the blockstate loader seems to completely ignore the textures tag.

 

(I haven't experimented with items, so I can't help as much there.)

Edited by DefectiveProgram
Link to comment
Share on other sites

Using perspective handling from previous post I just got working block & item rendering from custom OBJ model, there is code snipped if it helps:

    private static final TRSRTransformation THIRD_PERSON_BLOCK = Transforms.convert(0, 2.5f, 0, 75, 45, 0, 0.375f);
    private static final ImmutableMap<TransformType, TRSRTransformation> BLOCK_TRANSFORMS = ImmutableMap.<TransformType, TRSRTransformation>builder()
            .put(TransformType.GUI, Transforms.convert(0, 0, 0, 30, 225, 0, 0.625f))
            .put(TransformType.GROUND, Transforms.convert(0, 3, 0, 0, 0, 0, 0.25f))
            .put(TransformType.FIXED, Transforms.convert(0, 0, 0, 0, 0, 0, 0.5f))
            .put(TransformType.THIRD_PERSON_RIGHT_HAND, THIRD_PERSON_BLOCK)
            .put(TransformType.THIRD_PERSON_LEFT_HAND, Transforms.leftify(THIRD_PERSON_BLOCK))
            .put(TransformType.FIRST_PERSON_RIGHT_HAND, Transforms.convert(0, 0, 0, 0, 45, 0, 0.4f))
            .put(TransformType.FIRST_PERSON_LEFT_HAND, Transforms.convert(0, 0, 0, 0, 225, 0, 0.4f))
            .build();

    @SubscribeEvent
    public static void onModelBakeEvent(ModelBakeEvent event) {
        try {
            IUnbakedModel model = ModelLoaderRegistry.getModelOrLogError(new ResourceLocation("toomanyores:block/water_wheel.obj"),
                    "Missing water wheel model");

            if (model instanceof OBJModel) {
                IBakedModel bakedModel = model.bake(event.getModelLoader(), ModelLoader.defaultTextureGetter(),
                        new BasicState(model.getDefaultState(), true), DefaultVertexFormats.BLOCK);

                IBakedModel bakedInvModel = model.bake(event.getModelLoader(), ModelLoader.defaultTextureGetter(),
                        new BasicState(model.getDefaultState(), true), DefaultVertexFormats.ITEM);
                bakedInvModel = new PerspectiveMapWrapper(bakedInvModel, BLOCK_TRANSFORMS);


                event.getModelRegistry().put(new ModelResourceLocation("toomanyores:waterwheel", ""), bakedModel);
                event.getModelRegistry().put(new ModelResourceLocation("toomanyores:waterwheel", "inventory"), bakedInvModel);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.