Skip to content

Instantly share code, notes, and snippets.

@lehjr
Forked from williewillus/Primer.md
Created February 3, 2016 22:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lehjr/fe082dc9b29e6798de40 to your computer and use it in GitHub Desktop.
Save lehjr/fe082dc9b29e6798de40 to your computer and use it in GitHub Desktop.
1.8 rendering primer

1.8 Rendering Primer by williewillus (formatted to markdown by gigaherz)

Note: This primer assumes you are using MinecraftForge 1.8.9 build 1670 or above. Correctness not guaranteed otherwise.

This guide is intended for those with a clear knowledge of general modding and want a quick up to speed on how new things work. If you are confused, please hop on IRC and ask for help!

Blocks and Items

  • 1.7: EVERY BLOCK SHAPE EVER was hardcoded into RenderBlocks. Oh God, just look at that class. Actually don’t, if you value your sanity.
  • 1.8: Block shapes are all declared using models
    • ++: Anyone can override block shapes and textures using resource packs
    • +++: Multithreaded - (way) faster. In my personal experience, a moderately sized 1.7 modpack (50-ish mods) runs about 20-35 fps at all times, with FastCraft. The same sized 1.8 modpack (Ludus/Pioneers) runs almost always above 40, reaching 60 when I’m not moving, and going above 80 when underground. Nonscientific, of course. YMMV
    • ++: Can use alternate model formats (OBJ, B3D, etc.)
    • -: New
    • -: Not much documentation. Let’s rectify that!
  • Knowledge about block states is necessary to understand models
    • Abstractions away from the meta numbers
    • Eventually metas will go completely away
    • Declare properties that your block should have
      • Do this by overriding createBlockState(), returning a new BlockState object, passing it your block object and all properties that it should have
      • In your block’s constructor, always set your default state by calling setDefaultState(blockState.getBaseState().withProperty(...)) for ALL properties
      • Vanilla provides us PropertyInteger, PropertyBool, and PropertyEnum. Each has a factory method to create them, and are kept as static final fields in the Block class or together in some state property class. Enums used in a PropertyEnum must implement IStringSerializable. The PropertyEnum factory also takes a Predicate filter, allowing to use a subset of an enumeration as valid values.
        • SIDE NOTE: I personally HIGHLY ADVISE you, if your mod has an API, to expose your blockstate properties in your API. This allows other mods to use setBlockState with your block without mucking around with metas, which is the point of these states.
      • Override getMetaFromState and getStateFromMeta to specify how your properties map to integer metas.
      • WARNING: Your state->meta and meta->state methods must be one to one inverses of each other. Each combination of property-values must map to the same integer and vice versa. Else you will encounter strange errors in your states later.
        • When Minecraft starts, it enumerates the extensive bi-mapping of metas and states. If you touch any of your properties that are for rendering-only (getActualState(), you’ll understand shortly) the bi-mapping will no longer be one to one and you will get extremely confusing bugs where the wrong properties are set on your block. Don’t touch any properties in these two methods except those that you need saved to meta. (This actually isn’t what precisely happens, but at a higher conceptual level, this is the reasoning behind it)
    • An IBlockState object represents a block and a collection of properties and values. It is similar to Block + meta in 1.7- but can also contain information about properties that are not saved in meta.
    • Whenever you do World.getBlockState(BlockPos) you receive an IBlockState that describes the meta.
    • We add values to properties not needed in the meta, but needed for rendering in getActualState(). Fences use this to check their side connections. You can also query fields from TE’s here (with appropriate null checks please!). This used to be hardcoded in RenderBlocks in 1.7 but can now be dispatched like this (can now use inheritance, check your TE’s, etc.).
    • You can make your own subclasses to IProperty as well!
    • Example:
public class MyBlock extends Block {
    public static final PropertyEnum<EnumDyeColor> COLOR = PropertyEnum.create("color", EnumDyeColor.class);
    public static final PropertyBool RENDERBOOLPROP = PropertyBool.create("boolean");

    public MyBlock() {
        setDefaultState(blockState.getBaseState().withProperty(COLOR, EnumDyeColor.WHITE).withProperty(RENDERBOOLPROP, false)); // Set default for ALL properties.
    }

    @Override
    public BlockState createBlockState() {
        return new BlockState(this, COLOR, RENDERBOOLPROP); // Declare the properties we have
    }

    @Override
    public int getMetaFromState(IBlockState state) {
        // Ignore bool prop in meta
        return state.getValue(COLOR).getMetadata();
    }

    @Override
    public IBlockState getStateFromMeta(int meta) {
        return getDefaultState().withProperty(COLOR, EnumDyeColor.byMetadata(meta));
    }

    // Notice the above two methods are one to one inverses, and DO NOT TOUCH RENDERPROPBOOL

    @Override
    public IBlockState getActualState(IBlockState metaState, IBlockAccess world, BlockPos pos) {
        // Use this method to add values to properties that aren't saved in meta. Fences use this. You can query your TE here too, just null check it first
      MyTeType te = (MyTeType) world.getTileEntity(pos);
        return state.withProperty(RENDERBOOLPROP, te != null && te.myBoolTeField);
    }
}
// in other places:
// setting meta to other things
world.setBlockState(pos, state.withProperty(COLOR, EnumDyeColor.GREEN));

// getting things (this does not consider getActualState), only the raw, saved to disk "meta" state.
if (world.getBlockState(pos).getValue(COLOR) == BLACK) {
    do stuff
}
  • Forenote: Any method calls to ModelLoader.* or similar provided below are to be done in preinit, usually immediately after you register your items and blocks.
  • Variants and the blockstate json
    • The blockstate json, located at blockstates/<registrynameofyourblock>.json, specifies the mapping from specific property-value pairs to models in the world
    • Using the Mojang format, you must enumerate a string for every. possible. combination. of properties and values. It also forces you to have separate files for all blocks and item models. We don’t like that. If you are a masochist and somehow enjoy 4 petabytes of JSON files (now a myth. if you still believe you need that, read the next bullet), see the vanilla wiki
    • The wonderful Forge Blockstate json format allows you much more power. You will save MANY json files from using it so it is recommended you learn this fast and ditch the vanilla format ASAP. A specification/full grammar is available here
    • This specification will be your BEST FRIEND through your 1.8 rendering journey. Bookmark it and look at it. A lot. The way it’s written is weird at first, but it’s a full grammar definition of what you can and can’t write. If you have questions, ask in #minecraftforge. Example:
{
    "forge_marker": 1,
    "defaults": {
        "model": "modid:orientable_slab",
        "textures": {
            "front": "modid:blocks/slabfurnacefront_off",
            "side": "...",
            "top": "...",
            "bottom": "..."
        },
        "transform": "forge:default-block"
    },
    "variants": {
        "active": {
            "false": {},
            "true": {
                "textures": {
                    "front": "modid:blocks/slabfurnacefront_on"
                }
            }
        },
        "half": {
            "lower": {},
            "upper": {
                "uvlock": true,
                "x": 180
            }
        },
        "facing": {
            "north": {},
            "south": { "y": 180 },
            "west": { "y": 270 },
            "east": { "y": 90 }
        },
        "active=false,facing=south,half=upper": [{
            "model": "minecraft:cube_all",
            "textures": {
                "all": "minecraft:blocks/diamond_block"
            }
        }]
    }
}
  • ...

    • Stepping through the example (look at the specification and at the example as you follow along):
      • Consider a block that has a PropertyEnum<EnumFacing> called "facing", a PropertyBool called "half", and a PropertyBool called "active". It's a slab furnace, basically.
      • Consider the blockstate json
      • The first line marks the JSON for Forge loading, instead of vanilla’s inferior loader
      • The “defaults” section specifies attributes that apply to all variants. Here we state that all variants should have a slab model with facing, and apply some default textures. We also set a default transform, that will automatically rotate our block in third person to look correct (vanilla hard-copy pastes the numbers into every itemblock model json -.-)
      • Now, the variants section
        • Instead of mapping combinations of values -> models like in vanilla, we instead specify the impact that a specific value has on the base model
        • Now take a look, we specify all properties and ALL of its values and their impact on the base model. If there is no impact, that value should still be there, but empty. In this case, we override the front texture if the furnace is on. All the other textures “cascade down” from the defaults block
        • For “half”, if the slab is in the upper part of the block, we rotate 180 degrees on the x axis, and lock the texture in place so it doesn’t rotate with our block (if we didn’t lock, we would get the lower half of the texture flipped upside down, instead of the upper half of the texture right side up)
        • For “facing”, we apply a global rotation on the y axis so the furnace faces the right way.
        • The final variant is more interesting. First, notice the brackets around the value of the key. This signals to the forge loader that “active=false,facing=south,half=upper” is not the name of a property, but instead is a full variant name, like the vanilla blockstate format. Indeed, we can specify the full vanilla-style variant name like this and it will override everything we specify above except in defaults. In this case, we reset the model to that of the diamond block.
        • You can also put more variants inside the json array (square brackets), the game will then choose randomly between them. Consider this JSON of Botania Alfglass
          • Instead of [{ }] in normal, we specify four different variants with overriden textures (each “{}” is a variant) and the game will randomly choose between them
    • Statemapping
      • Statemapping is the game’s way of figuring out which IBlockStates go to which variants inside the blockstate json.
      • By default the game takes your IBlockState, lists all the properties and their values in alphabetic order in the format “property=value,property=value,...”, then looks in blockstates/<registrynameofyourblock>.json for variants matching that string. (Side note: what you return from your Enums when implementing IStringSerializable will show up here as the value) If your block has no properties, it looks for a variant called “normal”.
        • Side note: How does the forge json magically work with this? Simple, it actually generates variants for each of these big full strings behind the scenes for you, so you don’t have to write them all out yourself like in vanilla
      • You can provide your custom implementation of IStateMapper, or use StateMap.Builder() for some common use cases:
        • Ignore one property completely for rendering: ModelLoader.setCustomStateMapper(myBlock, new StateMap.Builder().ignore(REDSTONE_POWER).build()); This is incredibly useful when you extend a vanilla block class but don’t care about any of the properties that it has (you’re forced to declare them in createBlockState(), or else the superclass’s call to setDefaultState() will fail).
        • Split a large blockstate json into several based on one property: ModelLoader.setCustomStateMapper(myBlock, new StateMap.Builder().withName(ENUM_PROP).build()); For example, vanilla stone block uses this to split the blockstate file for minecraft:stone into several based on the property BlockStone.VARIANT. Instead of blockstates/stone.json, the game looks for files matching all values of VARIANT. That means it looks for blockstates/andesite.json, blockstates/diorite.json, etc.
        • Append a suffix automatically to all search paths: Commonly used with the above, appends a specific string to all blockstate search paths. Used in vanilla with slabs, stairs, double slabs, and walls.
  • Getting block models up and running

    • You don’t! The game automatically statemaps and scans blockstates/ for a json with the same name as your block’s registry name (or wherever a custom statemapper says to look) and loads everything for you. Tada!
    • Override getRenderType() in your block class as needed. Default is 3: static model with optional TESR. 2: Force TESR only. 1: Vanilla fluid renderer (You don't want to use this for mod fluids, use 3). -1: Skip all rendering. These should no longer be distinct render ID’s as they were in 1.7.10 and below.
      • Gotcha: If you extend BlockContainer (which you shouldn’t be, that class is legacy/vanilla code), it automatically overrides getRenderType() with -1. Check it if your models just don’t render anything at all.
    • Writing JSON block models
      • Refer to the vanilla wiki
      • Use a modeler such as MrCrayfish’s or opl’s
      • Notes:
        • Many basic shapes are already available to you in Minecraft such as cube_all, orientable, cube_bottom_top, etc. Look inside assets/minecraft/models/block.
        • UV’s are always out of 0-16. All textures must be square multiples of 16 (see the vanilla wiki article on resource packs and textures). Thus your UV’s may be decimal if your texture is a multiple of 16. Prefer separate textures rather than large texture sheets (which will make the atlas less efficient because of large gaps in texture sheets). In other words, do not just resize and throw your techne texturemaps onto the atlas because people will hate you.
        • if you override colorMultiplier() or any of the other coloring methods in your block class, make sure each face of your model that you want to color specifies the “tintindex” tag (see the vanilla wiki page), where the number you specify is the render pass. Usually, 0 will do, play around with it otherwise.
  • Important interlude: ModelResourceLocation

    • When working with models, you will come across many of these objects. It is very important to remember that a ModelResourceLocation always refers to a variant within a blockstate json definition. There is only ONE special case in vanilla contrary to this, and Forge gets rid of the requirement of being forced to use that special case.
  • Getting item models up and running

    • 1.8 is kinda half complete in this regard, there is a whole concept of statemapping and automatic loading for blocks, but similar mechanisms are missing for items (possibly coming in 1.9)
    • Items have no concept of state and are thus stuck with metas.
    • You must set the ModelResourceLocation of every item+meta with the following call: ModelLoader.setCustomModelResourceLocation(myItem, <meta>, new ModelResourceLocation(<path>, <variant>));
      • You may see other sources on the internet recommend using Minecraft.getMinecraft().getRenderItem().getItemModelMesher().register(). However, this is old, not guaranteed to work, and was used in the dark early days of 1.8.0 when there was no custom model loading and Forge provided model hooks. use setCustomMRL instead.
      • You MUST call this during preInit. Your item models will be missing if you do it any later.
    • For items with no subtypes (tools, etc.), simply call this on meta 0.
    • Remember what we discussed a ModelResourceLocation was above: a variant inside a blockstate json, save for one special case. This is that special case. Vanilla forces us to use the variant “inventory”, which it will then special case and automatically look inside model/item/ for a model json.
    • Forge undoes this, and allows us to use our blockstate json to define an item model!
    • A simple block and its item form, fully defined using the forge json format (see code block below)
      • The filename would be assets/<modid>/blockstates/<registration name>.json
    • Remember from above, that the [ ] are needed so forge does not think “inventory” is the name of a blockstate property and try to find values underneath it.
    • You can use any other variant too! just pass something else into <variant>. I could have the item model be new ModelResourceLocation(<path>, “facing=north,powered=false”); etc. etc. This allows you to fully define inventory models for subblocks with a single variant.
    • If you are using an older version of Forge (below 1649): Whenever you setCustomModelResourceLocation to a path that is not the registry name of your item, or you use a variant that is not “inventory”, you must make the model loader aware of that by calling ModelLoader.registerItemVaraints(item, ModelResourceLocation…). This signals to the model loader “these models are going to be used, load and bake them please (or ensure that that is already done)”. In build 1650 and above, this is done for you automatically in setCustomModelResourceLocation, so you only need to do registerItemVariants if you use the dynamic callbacks (see “Switching models based on the ItemStack”)
    • Writing item models:
      • Again, refer to vanilla
      • Notes:
        • the layers “layer0”... correspond to the render passes of your item when being rendered. So if you override Item.getColorFromItemStack(), make sure the renderpass matches the layer number you wish to color. If you don’t use builtin/generated but still want to color, specify tintindex like you did with blocks above.
        • In vanilla, the number of layers is limited to five (layer0 to layer4). However, Forge removes that limitation. Yay!
    • Switching models based on the ItemStack
      • There are two ways to do this, for two different purposes
      • Item.getModel()
        • This callback is executed when rendering on players and is a stack, player, and item use time sensitive version of determining what model to render. This is most commonly used for custom bows. An example
        • All model paths you use must be declared beforehand in preinit using ModelLoader.registerItemVariants() so the loader can load and bake them.
      • ModelLoader.setCustomMeshDefinition()
        • This call accepts an item object, and an ItemMeshDefinition, which is a SAM interface (lambda-ready!) accepting an ItemStack and returning a ModelResourceLocation
        • You can do things like return different models depending on NBT, etc.
        • Prefer this to Item.getModel if you don't depend on checking the player or use time.
        • As with the above, you must declare all model paths you want to use beforehand using ModelLoader.registerItemVariants() so the loader can load and bake them.
    • Using a TESR for item model (NOT RECOMMENDED)
      • Convert your things to JSON
      • Animation API will be out soon, so if your thing needs it: when it does, follow bullet #1
      • You cannot set transformations for thirdperson, etc. Too bad.
      • This is just a temporary shim, and if you need it you will know where to find it
{
    "forge_marker": 1,
    "defaults": {
        "model": "minecraft:cube_all", // points to assets/minecraft/models/block/cube_all
        "textures": {
            "all": "minecraft:blocks/diamond_block" // cube_all needs a texture called "all", we provide it. This path resolves to assets/minecraft/textures/blocks/diamond_block.
        },
        "transform": "forge:default-block" // Convenience feature by forge, auto transform our block so it will look right in third person mode, etc.
    },
    "variants": {
        "normal": [{}] // This block has no properties and thus just has one in-world variant, "normal". It is left empty because it doesn't change from the defaults defined above. Notice the array enclosing the empty json object. This is important as it tells the forge blockstate deserializer that "normal" is the name of a variant, not the name of a property for which we need to supply values inside (see the forge blockstate specification)
        "inventory": [{}] // Ding ding ding, Forge lets use also use "inventory" to refer here, thus model is inherited from defaults and we're left with one forge blockstate json,  instead of a vanilla one, a block model, and an item model separately.
    }
}
  • Advanced manipulation of models

    • IModel - Represents the unbaked form of a model once it has been loaded. Usually a direct representation of the source of the model (Deserialized form of a JSON, simple OBJ container with all the information in it, etc.). Bake() is called on these to bake them.
    • IBakedModel - Represents the baked form of an IModel. Everything is optimized, reduced, and packed into neat lists of BakedQuads (which themselves hold vertices that can just be shot into the Tessellator and rendered).
    • IPerspectiveAwareModel
    • BakedQuad
    • UnpackedBakedQuad
    • ColoredBakedQuad
    • OBJ Models
    • B3D Models
    • Custom model loaders - You can write your own custom model loaders that implement ICustomModelLoader which allows you very fine grained control over what happens when someone specifies a certain type of model in a json’s “model” key. For instance, you can write a FancyPantsModelLodaer that accepts all declaration of “model”: “mymod:test.FANCYPANTSMODEL” since FANCYPANTSMODEL is a model format only your loader can understand. Theoretically someone could write an ICustomModelLoader that loads text files written in BFON (Brainfuck Object Notation, not an actual thing) into the model system and bakes them!
  • ISmartBlockModel

    • Special fake “baked model” that accepts an IBlockState and is allowed to dynamically in Java code decide what IBakedModel to return back.
  • Unlisted properties and Extended States

    • How are ISmartBlockModels in any way useful?
    • Enter Extended States and Unlisted Properties
    • All the properties we described above (Bool, Int, Enum), are known as listed properties because they show up in the vanilla F3 debug screen when hovering over a block.
    • Forge adds unlisted properties, which can have any component type you want. Common examples of unlisted properties are some sort of PropertyString (implementing IUnlistedProperty<String>) or even a PropertyState (implementing IUnlistedProperty<IBlockState>. See the Camoflage Block case study below).
    • You declare unlisted properties by returning an ExtendedBlockState in createBlockState(), passing in your block object, all listed properties, then all unlisted properties.
    • Make sure to set defaults for your unlisted properties in setDefaultState! Cast blockState to an ExtendedBlockState, set default values for all your unlisted properties, then set default values for all your listed properties
    • Just as getActualState enhances the metadata IBlockState with extra values for listed properties, getExtendedState enhances the state with extra values for unlisted properties. It is very much recommended to first enhance the IBlockState you get using getActualState() before enhancing it with your unlisted properties.
  • Back to ISmartBlockModel

    • The IBlockState you are passed in handleBlockState() is the result of getExtendedState(). If you followed the above recommendation, It is now completely filled with all the rendering information it can have. Thus you can now do special dynamic things using the information provided to you by unlisted properties. The opportunities are endless here. Some mods use this method to generate new models dynamically and cache them. Others use it to dynamically reprocess the old model.
    • Keep in mind this is only called once when a chunk section re-renders, it is not dynamic. (which would defeat its performance benefits).
    • Important concept: Your handleBlockState() could possibly be called from another chunk batching thread. It is heavily discouraged to query other information at this point since you will have to deal with threading. Conceptually it’s different: you don’t use handleBlockState to get extra rendering info you need, you should enhance your blockstate with the ALL the information it needs to render properly (recommended immutable copies of this information as necessary), and handleBlockState should decide based on your blockstate.
  • ISmartItemModel

    • Special fake “baked model” that accepts an ItemStack and is allowed to dynamically in Java code decide what IBakedModel to return back.
    • In both of the previous dynamic item model approaches, you had to know every model you were going to use ahead of time. That goes away with this. You can autogenerate models, steal models from other things, etc.
  • Where and when do I register a smartmodel?

    • Listen for ModelBakeEvent, which is fired after all models on the disk have been loaded and baked. At this point, place your smartmodel into the registry by calling event.modelRegistry.putObject(<ModelResourceLocation>, <yoursmartmodelobject>), where ModelResourceLocation is the variant you want to replace.
  • Caveats

    • Using static models for overly dynamic things:
      • Although new models are multithreaded and faster, if you need highly dynamic things that need to be updated more than once every 10-ish seconds or so, use a TESR. In most cases use models for the static part and render the dynamic part using a TESR (this is also possible using the Animation API)
      • Changing models rapidly forces the entire 16x16x16 chunk section to be redrawn, which is worse than one TESR dynamically updating every tick.
    • Dynamicism reduces flexibility:
      • It may be tempting to just say “screw it” to the json system and implement smart models that regenerate everything for you based on your textures and your custom fancypants system, etc. But it is wise to keep in mind that every additional thing you move into a dynamic, Java-controlled system is reducing the amount of customizability someone else can do using resource packs, as well as a performance decrease. Please use your best judgment.
  • Some case studies requiring extra-ordinary mechanisms

    • Aura Cascade’s Fortified Blocks:
      • Aura Cascade has Fortified Blocks, which are standard full cube blocks, but each meta value is representative of a listed integer property, DAMAGE. As a block is damaged, the block breaking cracks appear on it.
      • How can we do this without manually editing the block breaking crack onto every texture?
      • Accomplished using Forge’s MultiLayerModels.
      • Blockstate json
      • Explanation:
        • We specify our model as “forge:multi-layer”. A custom model loader provided by Forge intercepts this and prevents vanilla from trying to load it (since there obviously exists no multi-layer.json)
        • Under each variant, we specify a “custom” tag which allows us to pass custom information directly to the model loader. Here, the MultiLayerModel loader expects us to give it something for “base”, and one for each of the applicable render layers. These strings look familiar in formatting, and if you’re guessing that they’re ModelResourceLocation’s, you’d be right! You specify a variant within a blockstate json to use for each render layer (or some path that a smartmodel will occupy)
        • “Base” is used for things like the block breaking particle, transforms, and other things you can’t simply combine from multiple models.
        • For the solid layer, we specify the “base” variant, a simple model with the same texture on all sides.
        • For the translucent layer, we specify variants that contain a simple cube_all model with the vanilla block breaking textures on all sides.
        • At runtime, the MultiLayerBakedModel implements ISmartBlockModel and dynamically grabs the two or more variants we specified here in “custom”, checks the render layer, and returns the appropriate one.
        • Note: In the block class’s canRenderInLayer method, return true for the specific layers that you want to render in
    • Botania’s Special Flowers:
      • Botania’s magical flowers are all under one block ID: botania:specialFlower. However, addon developers can register an arbitrary amount of new flowers and they all go under this one block ID.
      • How can we have an unlimited and arbitrary amount of models for a single block ID?
      • Solved using a custom model loader.
      • specialFlower blockstate json
      • SpecialFlowerModel, model loader, and baked form here
      • Stepping through SpecialFlowerModel:
        • The custom model loader (Line 133) is simple, and says “For every blockstate json that says its model is ‘botania_special:specialFlower’”, volunteer to load it and return a dummy IModel INSTANCE.
        • Meanwhile, as preinit occurs, mods call the external API, providing ModelResourceLocations they want to use for the item and block forms of a specific type of magical flower.
        • SpecialFlowerModel specifies all of the ModelResourceLocations it’s accumulated as dependencies so the game loads and bakes those models.
        • Baking the SpecialFlowerModel does nearly nothing, it just passes all the ModelResourceLocations the API has accumulated into a SpecialFlowerBakedModel
        • SpecialFlowerBakedModel is a smart item and block model, so during runtime, it is passed an ItemStack/IBlockState and asked for an IBakedModel back. SpecialFlowerBakedModel only knows what types of magical flowers map to which ModelResourceLocations, it contains no actual vertex information. So during runtime, SpecialFlowerBakedModel looks at the type of the magical flower (either from examining an unlisted PropertyString in an IExtendedBlockState or from examining the NBT of an ItemStack), then goes the the model registry, steals the baked model corresponding to the correct ModelResourceLocation, and returns it. Magical!
        • Caveats: All models must be renderable on the CUTOUT_MIPPED render layer. No translucent models, sorry! Other render layers are possible but will needlessly overcomplicate the codebase
    • Botania’s Platforms (Camouflage Blocks):
      • Botania’s Platforms are camouflaging blocks, able to take on the looks of other blocks when right clicked
      • The platform has three types, Abstruse, Spectral, and Infrangible. These are stored in a listed property and saved to meta.
      • The block uses extended blockstates to have an unlisted property with component type IBlockState, representing the mimicked blockstate.
        • The unlisted property is filled in getExtendedState by querying the tile entity
        • Notice how we say our block can render in every renderpass. This’ll be important later if we want top notch mimicry.
      • The platform has a TileEntity, that stores a single IBlockState that represents the mimicked state
      • The blockstate json is a standard one, having a simple model for each variant. The inventory models are registered as usual.
      • We use a custom statemapper to redirect all different types of platform to the single modelresourcelocation “botania:platform#normal”
      • Then we listen to ModelBakeEvent and insert the smartmodel into the entry for “botania:platform#normal”
        • Now it is immediately evident in our smartmodel what all our preparation has done.
        • First we check the layer and if the tile entity actually has something to mimic. If no mimic and the render pass is for solid blocks, then go ahead and just render our normal model out of the blockstate json
        • If we do have a camo, then check if that block is wiling to render in the current layer. If so, steal its model and return it, otherwise just return the smartmodel itself as the bakedmodel to render. Since the smartmodel itself defines no quads (see getGeneralQuads() and getFaceQuads()) nothing will render, effectively skipping the renderpass. Doing this allows the platform to mimic translucent blocks like stained glass correctly, which it did not do correctly in Botania for MC 1.7.10
        • Notice we check if the mimicked block also has a smartmodel, allowing it to itself handle special rendering.
    • Botania’s Pylons: <TBD when OBJ group visibility is fixed>
    • Botania’s Floating Flowers: <mashing other models together during runtime>

Tile Entities

  • Literally nothing changed. Hooray for you!
  • Move as much as you can (within reason, see “Using static models for overly dynamic things” above) to a static, model system model. Every additional thing you move out of a TESR is something that no longer is redrawn every frame, but is only updated and redrawn when the chunk updates. Remember that you can use both static models and a TESR if you return 3 in getRenderType(). An example of this is in vanilla: the enchanting table base is a json model, only the book is TESR rendered. Another example is the Botania 1.8 petal apothecary: the entire goblet is a json model, only the liquid and the ingredients swirling inside are using a TESR. Framerate increases will quickly accumulate the more modders do this!
  • If you need to render an item model on your tile entity, use Minecraft.getMinecraft().getRenderItem().renderItem(ItemStack, ItemCameraTransforms.TransformType). Usually you will use TransfomType.NONE, but occasionally TransformType.GROUND or TransformType.THIRDPERSON will be useful.
  • If you need to render a block on your tile entity, use Minecraft.getMinecraft().getBlockRenderDispatcher().renderBlockBrightness(IBlockState, brightness)
    • This doesn’t seem to render modded TESRs that have a static item model. Awaiting an answer from Forge.

Entities

  • Core mostly unchanged

  • Specials subclass of RenderPlayerEvent has been deprecated. Auxiliary renders (held item, armor, stuck arrows, etc.) now use LayerRenderers. Look at the LayerRenderer interface for more information and look at the vanilla implementations of it for examples of how to use it. Adding LayerRenderers to entities are done by obtaining the entity’s Render object and calling addLayer() on it. Layers are only supported on subclasses of EntityLivingBase.

    • You can obtain a non-player entity’s Render object using Minecraft.getMinecraft().getRenderManager().entityRenderMap.get(entity.class);
    • To get players’ Render objects, use: Minecraft.getMinecraft().getRenderManager().getSkinMap().get(<SKINTYPE>). Where SKINTYPE can be “default” for Steve model, and “slim” for the Alex model.
  • To render item and block models, use the same methods in the TileEntity section above

    • If you want the Item.getModel() hooks to trigger, instead use renderItemModelForEntity(stack, entitylivingbase, TransformType) which will check if the entity is a player and call getModel() accordingly.

Miscellaneous

  • I need to render dynamic text! Models suck! something something IItemRenderer!

    • In the not-accurate words of Fry, “I got FontRenderer half working for models almost a year ago, but the people wanting it ragequit. So I moved to more important things”

      • Half working things here
    • Moral: If you need something in Forge, request it. Someone will either highlight a solution for you, or write one for you, if your request is feasible, sensible, and would benefit other modders.

  • There are no longer two separate texture sheets for blocks and items, the only texture sheet is TextureMap.locationBlocksTexture.

  • If you had arbitrary IIcons that had nothing to do with blocks or items or entities that you used for auxiliary rendering (overlays, etc.). You now simply need to listen for TextureStitchEvent.Pre and call event.map.registerSprite(ResourceLocation) and the texture will be loaded and stitched and you will receive a TextureAtlasSprite back, which is essentially the same thing as an IIcon. You can then render it with the tessellator. Keep in mind TextureAtlasSprite is a sideonly client class (unlike IIcon), do not let it get loaded on the server side or it will crash the dedicated server.

  • Tessellator changes from 1.7 and 1.8.0 -> 1.8.8 and 1.8.9

    • Instead of the Tessellator object controlling vertex calls, now the WorldRenderer does it. You get one by calling Tessellator.getInstance().getWorldRenderer()

    • In 1.8.0, that’s basically the entire difference (just stick getWorldRenderer() in front of everything). In 1.8.8, that’s not the case.

    • WorldRenderer.startDrawing(int) became WorldRenderer.begin(int, VertexFormat).

    • You now use the WorldRenderer to “build up vertices” to draw.

    • The first int parameter to begin() is a GL mode. For almost all of your old cases (startDrawingQuads()), this will be GL11.GL_QUADS (Seen as “7” throughout the vanilla codebase).

    • The second is a VertexFormat, which specifies the format of the vertices you will be building with the tessellator. Will it be colored? Will it use UV’s from a texture sheet? Will it have normals?

      • Look in DefaultVertexFormats class. There will be a ton of static pre-made VertexFormats for you to use. For example, if you just want colored vertices, you would use POSITION_COLOR. If you wanted vertices from a texture sheet, use POSITION_TEX. If you wanted colored vertices from a texture sheet, use POSITION_TEX_COLOR. And so on and so forth
      • If in the off case what you need isn’t there and you know what you’re doing, construct your own.
    • Now that we’ve gotten through the begin() call, let’s build the vertices

      • You build vertices by going through all the attributes you specified in your vertex format. For example, if you chose POSITION_TEX, then you add a vertex by calling worldRenderer.pos(x, y, z).tex(u, v).endVertex(). The endVertex() call is required.
      • Important Note: You must use ALL of the attributes that you declare in your vertex format, in order. If you begin with vertex format POSITION_TEX_COLOR, then every vertex must have a pos() call, followed by a tex() call, followed by a color() call, followed by endVertex(). This is a change from 1.8.0 and below, where you could just set the brightness or color once and add vertices.
    • After your vertices have been built, end it off with tessellator.draw(), just like before.

Remarks

  • Go spread the goodness of 1.8.x!!!
  • Thank fry (aka RainWarrior) for EVERYTHING. He has basically made modding 1.8 actually possible for all of us. He wrote the B3D loader, Forge json format, and nearly completely rewrote minecraft lighting to be more flexible. If you’re using something new that’s rendering related, he wrote it (unless it’s the OBJ loader, shadekiller wrote that)
  • If you have questions, lots of people in #minecraftforge will be MORE than eager to help you port.
  • If you have an advanced model question (need to do super dynamic stuff), ask fry and he will either tell you your idea sucks, teach you how/a better way, or write it if it’s not currently possible
  • An Animation API allowing less dynamic animations to be moved out of a TESR is out now, stay tuned for another gist about it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment