MCME Wiki

generateVanilla is the converter in ResourcePackScripts that turns the Sodium variant of a pack into its Vanilla variant, and, with --limit 2, its Lite variant. The Sodium variant's 3D models are .obj files that only render with the Special Model Loader client mod. generateVanilla bakes each of them with objmc into an ordinary block model plus a texture that also carries the geometry, and the shaders in the pack draw the real shape, so players see the same models without any mods. This page explains what it does, step by step. The rules this means for pack authors are in Pack repository.

Run it

You need Python 3.10 or newer. In a checkout of ResourcePackScripts:

pip install -r requirements.txt
python generateVanilla/generateVanilla.py <sodium pack> <output folder> <vanilla resources> --objmc generateVanilla/objmc.py
Option What it does
--objmc PATH The objmc script to bake with. Default: objmc.py in the current folder.
--limit N Keep only the first N models of each blockstate list. The Lite variant uses --limit 2. N must be 1 or more.
--compress Write the generated JSON without indentation.
--debug Print every step. The same lines go to debug.log.
--noblocks Skip the blockstates.
--noitems Skip the item definitions.

Overview

flowchart TD
    A["pack.mcmeta, pack.png, license.txt, README.md"] --> OUT["Output pack"]
    B["assets/, except the rebuilt folders"] --> OUT
    C["vanilla/assets/, except the rebuilt folders"] --> OUT
    D["Overlay folders"] --> OUT
    S["The shader base"] --> OUT
    V["Vanilla resources: the list of blockstates and items"] --> W["Walk every blockstate, then every item"]
    W -->|"mcme: model"| OBJ["Bake with objmc"] --> OUT
    W -->|"other model"| CH["Copy what the pack overrides along its parent chain"] --> OUT

The input pack is only read. Temporary files go to a temporary folder.

1. pack.mcmeta and the top-level files

2. Copying assets

assets/ is copied as it is, except these folders in every namespace, because step 5 rebuilds them from what is used:

Skipped Why
blockstates, items Rewritten from the walk
models Only models the walk reaches are written
textures/block, textures/item Only textures those models use are copied
assets/mcme/sml_load_scopes Only Special Model Loader reads it

The rest of textures/ (gui, entity, font and so on) is copied whole. The modelengine namespace keeps its models and items, because its own loader reads them.

So a model, or a textures/block texture, that no blockstate or item definition reaches, directly or through a parent, is not in the output.

3. The vanilla folder and overlays

4. The shader base

The newest shader base and the pack's modules are written into the output, over the copies the pack's repository has. For the Lite variant (any run with --limit), its mcme_lite.glsl defines MCME_LITE, so fluids are drawn as plain textures.

This step stops the run if the pack's copy of a base or module file was changed by hand. At the end of the run, the converter also signs the fluid textures, checks that every shader import resolves and that no shader breaks a platform rule, and stops if not. See The shader base.

5. Walking the blockstates and items

The converter doesn't walk the pack. It walks the vanilla client's assets/minecraft/blockstates/*.json, then assets/minecraft/items/*.json, from the vanilla resources folder. For each name it takes the first of:

  1. the pack's vanilla/assets/minecraft/… file;
  2. the pack's assets/minecraft/… file;
  3. the vanilla client's own file.

Only files from 1 or 2 are written to the output. A vanilla client's file is only walked, to copy any models and textures the pack overrides along its chains.

So the vanilla resources decide what can be converted. A blockstate for a block that isn't in that version's list is never read, and is missing from the output. The same goes for anything outside the minecraft namespace. The server uses Minecraft 26.2's resources, so blocks added in 26.3 are dropped.

Each model entry then goes one of two ways:

The water and lava textures (block/water_still, water_flow, lava_still, lava_flow and their .mcmeta) are always copied from the pack if it has them, used or not.

Baking a model with objmc

objmc turns a 3D model into an ordinary block model plus a texture. The texture carries the geometry as pixel data next to the real picture, and objmc's core shaders, part of the shader base, read that data and draw the real shape. The baked models only render with those shaders. ResourcePackScripts bundles its own Python port of objmc as generateVanilla/objmc.py, for static block models.

For one mcme: model entry the converter:

  1. finds the files: the model JSON assets/mcme/models/<path>.json; the .obj named in its model key; the .mtl named in mtl_override, or else the one with the model's own name; and the optional .objmeta with the model's name;
  2. finds the texture: the .objmeta's texture, or else the .mtl's first map_Kd line. mcme: textures come from assets/mcme/textures/, minecraft: ones from the pack's assets/minecraft/textures/;
  3. rotates a temporary copy of the .obj, if the entry has a rotation (see Rotations);
  4. runs objmc with the .objmeta settings and four mipmap levels;
  5. fits the result into the pack: the baked model goes to assets/mcme/models/<path>[suffix].json and its texture to assets/mcme/textures/<path>[suffix].png. The texture is also the particle texture. display, gui_light and every tintindex are removed, so baked models aren't biome-tinted;
  6. shares the geometry, if the .obj is a shared parent (see Shared parents).

After every model is baked, bakes that hold the same picture are merged, so each texture is stored once.

A model whose JSON has no model key, or whose .obj is missing, is skipped without any message. A model that fails for another reason (a missing .mtl or texture, an objmc error) is skipped with a message. Either way the blockstate still names the model, so the block shows as a missing model in game.

What the baked texture looks like:

objmc's limits:

Conversion settings (.objmeta)

An optional YAML file next to the model JSON, with the same name. Every key is optional.

Key Default Effect
texture the .mtl's map_Kd The texture to bake from: mcme:block/x, or minecraft:block/x for one in the pack's own assets/minecraft/textures/ (not the vanilla client's)
output_texture the model's path Name of the baked texture, under assets/mcme/textures/
offset -0.5 0.0 -0.5 Moves the model before baking. Three numbers in one string, separated by spaces. A YAML list stops the run.
options none A list: noshadow turns off face shading; flipuv is for a texture that comes out upside down. Nothing else is recognised.
visibility 7 Where the model shows, as a sum: 4 in the world, 2 in the hand, 1 in the GUI
omnidirectional_parent false For a shared parent .obj only: the model looks the same from every side, so all its rotations share one parent

For example:

offset: "-0.5 0.0 -0.5"
options: [noshadow]
visibility: 6

Rotations

A blockstate entry's x, y or z rotation is baked into the model:

Shared parents

Many models use the same shape with different textures. The Vanilla variant can store that shape once, as a parent model, so the pack stays smaller.

Make every texture used with one parent .obj the same size. The geometry in a baked texture depends on the texture's size, so only bakes of the same size can share. When sizes differ you get … shares parent group … but its baked texture is …, not … - not sharing a parent with it. Treat it as an error and resize the textures.

Animated textures

A texture with a .png.mcmeta that has an animation section is treated as a flipbook:

What stops a run, and what doesn't

A problem with one model or file normally doesn't stop the run. The converter prints a message, skips that model or file, and carries on. Read the messages after every run: Troubleshooting lists them.

These stop the run with a Python error (exit code 1):

A missing or wrong vanilla resources folder doesn't stop anything: the walk finds no blockstates or items, writes none, and the run ends normally. Check that folder if the output has no blockstates.

Exit codes: 0 when the run gets to the end, even with warnings and skipped models; 1 for a stop; 2 for wrong command line arguments.

In a release, a stop ends the release with ERROR: generateVanilla stopped, and nothing is published.

Output and logging

Speed

Everything runs one after another, and baking dominates: every model entry is a separate objmc run of roughly 50 to 100 ms for a normal texture, more for big ones (about 1¼ s for 1024×1024). A model used by several blockstates is baked again for each one. On the release server a Human Vanilla variant (about 2,200 bakes) takes about 3½ minutes, and Lite about 2.

Other scripts in the repository

Standalone helpers that generateVanilla doesn't use:

Script What it does
generateVanilla/rotate_obj.py <file.obj> <axis> <angle> Rotates an .obj in place by 90, 180 or 270 degrees around x, y or z.
finder.py <model> Reports which blockstates and parent models use the block model <model> (name without block/). Read-only.
sorter.py Moves every block model and texture that no blockstate uses into unlinked_models/ and unlinked_textures/, without asking. Run it on a copy.

finder.py and sorter.py don't read a pack. They run in a folder laid out as blockstates/, vanilla_blockstates/, models/block/, vanilla_models/block/ and textures/block/.

The shader scripts (syncShaderBase.py, checkShaders.py, signFluids.py, applyShaderBase.py) are on The shader base.

Working on the converter

The code map, the tests and the rules the code follows are in the ResourcePackScripts README. Branch from development and open pull requests into it; a release is a pull request from development into master. Change the objmc shaders in the shader base together with objmc.py, in the same pull request.