User Tools

Site Tools


tutorial:custom_model

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:custom_model [2020/08/14 07:12] – [custom model] Add a note regarding more dynamic rendering technici4ntutorial:custom_model [2023/12/31 21:40] (current) – [Sprites] Missed `int`s gudenau
Line 10: Line 10:
  
 <code java> <code java>
 +@Environment(EnvType.CLIENT)
 public class FourSidedFurnaceModel implements UnbakedModel, BakedModel, FabricBakedModel { public class FourSidedFurnaceModel implements UnbakedModel, BakedModel, FabricBakedModel {
 </code> </code>
Line 18: Line 19:
 <code java> <code java>
     private static final SpriteIdentifier[] SPRITE_IDS = new SpriteIdentifier[]{     private static final SpriteIdentifier[] SPRITE_IDS = new SpriteIdentifier[]{
-            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, new Identifier("minecraft:block/furnace_front_on")), +            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_front_on")), 
-            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, new Identifier("minecraft:block/furnace_top"))+            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_top"))
     };     };
-    private Sprite[] SPRITES = new Sprite[2];+    private final Sprite[] sprites = new Sprite[SPRITE_IDS.length]; 
 + 
 +    // Some constants to avoid magic numbers, these need to match the SPRITE_IDS 
 +    private static final int SPRITE_SIDE = 0; 
 +    private static final int SPRITE_TOP = 1; 
 </code> </code>
  
Line 34: Line 40:
     @Override     @Override
     public Collection<Identifier> getModelDependencies() {     public Collection<Identifier> getModelDependencies() {
-        return Collections.emptyList(); // This model does not depend on other models.+        return List.of(); // This model does not depend on other models.
     }     }
  
     @Override     @Override
-    public Collection<SpriteIdentifier> getTextureDependencies(Function<Identifier, UnbakedModel> unbakedModelGetter, Set<Pair<String, String>> unresolvedTextureReferences) { +    public void setParents(Function<Identifier, UnbakedModel> modelLoader) { 
-        return Arrays.asList(SPRITE_IDS); // The textures this model (and all its model dependenciesand their dependencies, etc...!) depends on.+        // This is related to model parentsit's not required for our use case
     }     }
  
Line 46: Line 52:
     public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {     public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
         // Get the sprites         // Get the sprites
-        for(int i = 0; i < 2; ++i) { +        for(int i = 0; i < SPRITE_IDS.length; ++i) { 
-            SPRITES[i] = textureGetter.apply(SPRITE_IDS[i]);+            sprites[i] = textureGetter.apply(SPRITE_IDS[i]);
         }         }
         // Build the mesh using the Renderer API         // Build the mesh using the Renderer API
Line 55: Line 61:
  
         for(Direction direction : Direction.values()) {         for(Direction direction : Direction.values()) {
-            int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? 0;+            // UP and DOWN share the Y axis 
 +            int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? SPRITE_TOP SPRITE_SIDE;
             // Add a new face to the mesh             // Add a new face to the mesh
             emitter.square(direction, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);             emitter.square(direction, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
             // Set the sprite of the face, must be called after .square()             // Set the sprite of the face, must be called after .square()
             // We haven't specified any UV coordinates, so we want to use the whole texture. BAKE_LOCK_UV does exactly that.             // We haven't specified any UV coordinates, so we want to use the whole texture. BAKE_LOCK_UV does exactly that.
-            emitter.spriteBake(0, SPRITES[spriteIdx], MutableQuadView.BAKE_LOCK_UV);+            emitter.spriteBake(sprites[spriteIdx], MutableQuadView.BAKE_LOCK_UV);
             // Enable texture usage             // Enable texture usage
-            emitter.spriteColor(0, -1, -1, -1, -1);+            emitter.color(-1, -1, -1, -1);
             // Add the quad to the mesh             // Add the quad to the mesh
             emitter.emit();             emitter.emit();
Line 73: Line 80:
  
 ==== BakedModel methods ==== ==== BakedModel methods ====
-The methods here are not used by the Fabric Renderer, so we don't really care about the implementation.+Not all the methods here are used by the Fabric Renderer, so we don't really care about the implementation.
 <code java> <code java>
-@Override+    @Override
     public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {     public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {
-        return null; // Don't need because we use FabricBakedModel instead+        // Don't need because we use FabricBakedModel instead. However, it's better to not return null in case some mod decides to call this function. 
 +        return List.of();
     }     }
  
     @Override     @Override
     public boolean useAmbientOcclusion() {     public boolean useAmbientOcclusion() {
-        return false; // Again, we don't really care, etc...+        return true; // we want the block to have a shadow depending on the adjacent blocks
     }     }
  
     @Override     @Override
-    public boolean hasDepth() {+    public boolean isBuiltin() {
         return false;         return false;
     }     }
  
     @Override     @Override
-    public boolean isSideLit() {+    public boolean hasDepth() {
         return false;         return false;
     }     }
  
     @Override     @Override
-    public boolean isBuiltin() {+    public boolean isSideLit() {
         return false;         return false;
     }     }
  
     @Override     @Override
-    public Sprite getSprite() { +    public Sprite getParticleSprite() { 
-        return SPRITES[1]; // Block break particle, let's use furnace_top+        return sprites[SPRITE_TOP]; // Block break particle, let's use furnace_top
     }     }
  
Line 128: Line 136:
                  
         // We just render the mesh         // We just render the mesh
-        renderContext.meshConsumer().accept(mesh);+        mesh.outputTo(context.getEmitter());
     }     }
  
Line 137: Line 145:
 } }
 </code> </code>
 +
 +Note: Make sure you override the ''FabricBakedModel'' methods, the interface has ''default'' implementations!
  
 ===== Registering the model ===== ===== Registering the model =====
-Let's first write a ''ModelResourceProvider'', an interface that allows you to provide an ''UnbakedModel'' before the game tries to load it from JSON. Have a look at [[https://github.com/FabricMC/fabric/blob/1.16/fabric-models-v0/src/main/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java|the documentation]] for more details. The important part is that ''loadModelResource()'' will be called for every model.+In order for the model to be rendered in game we need to register it. In order to register it you need to create a ''ModelLoadingPlugin'': 
 +<code java
 +@Environment(EnvType.CLIENT) 
 +public class TutorialModelLoadingPlugin impelements ModelLoadingPlugin { 
 +    public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL = new ModelIdentifier("tutorial", "four_sided_furnace", "");
  
-Let's register the model under the name ''tutorial:block/four_sided_furnace''. 
-<code java> 
-public class TutorialModelProvider implements ModelResourceProvider { 
-    public static final Identifier FOUR_SIDED_FURNACE_MODEL = new Identifier("tutorial:block/four_sided_furnace"); 
     @Override     @Override
-    public UnbakedModel loadModelResource(Identifier identifier, ModelProviderContext modelProviderContextthrows ModelProviderException +    public void onInitializeModelLoader(Context pluginContext) { 
-        if(identifier.equals(FOUR_SIDED_FURNACE_MODEL)) { +        // We want to add our model when the models are loaded 
-            return new FourSidedFurnaceModel(); +        pluginContext.modifyModelOnLoad().register((original, context) -> { 
-        } else { +            // This is called for every model that is loaded, so make sure we only target ours 
-            return null+            if(context.id().equals(FOUR_SIDED_FURNACE_MODEL)) { 
-        }+                return new FourSidedFurnaceModel(); 
 +            } else { 
 +                // If we don't modify the model we just return the original as-is 
 +                return original; 
 +            } 
 +        });
     }     }
 } }
 </code> </code>
  
-Now we have to register this class in the client initializer, the entry point for client-specific code.+Then you need to register the plugin we just created:
 <code java> <code java>
 +@Environment(EnvType.CLIENT)
 public class ExampleModClient implements ClientModInitializer { public class ExampleModClient implements ClientModInitializer {
     @Override     @Override
     public void onInitializeClient() {     public void onInitializeClient() {
-        ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> new TutorialModelProvider()); +        ModelLoadingPlugin.register(new TutorialModelLoadingPlugin()); 
-        + 
         /* Other client-specific initialization */         /* Other client-specific initialization */
     }     }
Line 174: Line 190:
     /* ... */     /* ... */
     "client": [     "client": [
-      "tutorial.path.to.ExampleModClient"+      "net.fabricmc.example.ExampleModClient"
     ]     ]
   },   },
Line 197: Line 213:
  
 ==== Updating the model ==== ==== Updating the model ====
-We will re-use the same model class, with a few changes: +We will re-use the same model class, with just small change
-  * We will register the same model instance under a different name, so we'll make sure the model is only baked once. +  * We will need a ''ModelTransformation'' that rotates/translates/scales the model depending on its position (in right hand, in left hand, in gui, in item frame, etc...). As we are creating a model for a regular block, we can use the transform provided by fabric in ''ModelHelper.MODEL_TRANSFORM_BLOCK''.
-  * We will need a ''ModelTransformation'' that rotates/translates/scales the model depending on its position (in right hand, in left hand, in gui, in item frame, etc...). As we are creating a model for a regular block, we will use the one from "minecraft:block/block" which we will load during model baking.+
  
 We will update our ''FourSidedFurnaceModel'' class as follows: We will update our ''FourSidedFurnaceModel'' class as follows:
 <code java> <code java>
-    // The minecraft default block model 
-    private static final Identifier DEFAULT_BLOCK_MODEL = new Identifier("minecraft:block/block"); 
- 
-    private boolean isBaked = false; 
-    private ModelTransformation transformation; 
-     
-    // We need to add the default model to the dependencies 
-    public Collection<Identifier> getModelDependencies() { 
-        return Arrays.asList(DEFAULT_BLOCK_MODEL); 
-    } 
-     
-    // We need to add a bit of logic to the bake function 
-    @Override 
-    public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { 
-        // Don't bake twice 
-        if(isBaked) return this; 
-        isBaked = true; 
-         
-        // Load the default block model 
-        JsonUnbakedModel defaultBlockModel = (JsonUnbakedModel) loader.getOrLoadModel(DEFAULT_BLOCK_MODEL); 
-        // Get its ModelTransformation 
-        transformation = defaultBlockModel.getTransformations(); 
-         
-        /* Previous code */ 
-    } 
-     
     // We need to implement getTransformation() and getOverrides()     // We need to implement getTransformation() and getOverrides()
     @Override     @Override
     public ModelTransformation getTransformation() {     public ModelTransformation getTransformation() {
-        return transformation;+        return ModelHelper.MODEL_TRANSFORM_BLOCK;
     }     }
  
Line 249: Line 238:
     @Override     @Override
     public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) {     public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) {
-        renderContext.meshConsumer().accept(mesh);+        mesh.outputTo(context.getEmitter());
     }     }
 </code> </code>
Line 256: Line 245:
 Let's update the ''ModelResourceProvider'' we created earlier: Let's update the ''ModelResourceProvider'' we created earlier:
 <code java> <code java>
 +@Environment(EnvType.CLIENT)
 public class TutorialModelProvider implements ModelResourceProvider { public class TutorialModelProvider implements ModelResourceProvider {
     public static final FourSidedFurnaceModel FOUR_SIDED_FURNACE_MODEL = new FourSidedFurnaceModel();     public static final FourSidedFurnaceModel FOUR_SIDED_FURNACE_MODEL = new FourSidedFurnaceModel();
-    public static final Identifier FOUR_SIDED_FURNACE_MODEL_BLOCK = new Identifier("tutorial:block/four_sided_furnace"); +    public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL_BLOCK = new ModelIdentifier("tutorial", "four_sided_furnace", ""); 
-    public static final Identifier FOUR_SIDED_FURNACE_MODEL_ITEM = new Identifier("tutorial:item/four_sided_furnace");+    public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL_ITEM = new ModelIdentifier("tutorial", "four_sided_furnace", "inventory");
  
     @Override     @Override
tutorial/custom_model.1597389172.txt.gz · Last modified: 2020/08/14 07:12 by technici4n