Skip to content

CatFrame Universal Model Rendering Extension System

dfdvdsf edited this page May 31, 2026 · 4 revisions

Applicable to all rendering of blocks (in the world) and items (GUI / held) under the CatFrame JSON model pipeline. Design goal: allow mods to insert custom tinting, shadows, self-illumination, face culling, and other rendering effects through a unified registration point, without needing to mixin the rendering pipeline.


1. Architecture Overview

flowchart TD
    A[JSON Model] --> B[Parsing + Baking]
    B --> C[BakedQuad]
    C --> D[VanillaModelManager.render* per quad]
    D --> E[ModelRenderRegistry.apply]
    E --> F1[TintRenderExtension built-in]
    E --> F2[IModelRenderExtension #2]
    E --> F3[IModelRenderExtension #N]
    F1 --> G[Modify context: color, brightness, shade, skip]
    F2 --> G
    F3 --> G
    G --> H[Tessellator setColor + addVertex]
Loading

Three core classes (package decok.dfcdvadstf.catframe.model.render):

Class Role
IModelRenderExtension Functional interface implemented by mods, processes rendering parameters for a single quad.
RenderContext Mutable context for a quad; extensions read the environment and write color/brightness/culling.
ModelRenderRegistry Registry that chains extensions together in registration order.
RenderPhase Enum: BLOCK_WORLD / ITEM_GUI / ITEM_HAND.

Subpackage model.render.tint provides the built-in "high-version tintindex" implementation as an example.


2. Integration Process (Mod Side)

2.1 Register an Extension

@SideOnly(Side.CLIENT)
public class MyClientInit {
    public void init() {
        ModelRenderRegistry.register(ctx -> {
            // ① Handle only specific phases
            if (ctx.phase != RenderPhase.BLOCK_WORLD) return;

            // ② Target only your own block
            if (ctx.block != MyBlocks.CRYSTAL) return;

            // ③ Modify output fields
            ctx.brightnessOverride = 0xF000F0; // always full bright (self-illumination)
            ctx.mulColor(0xCCDDFF);            // add cool tint
        });
    }
}

2.2 RenderContext Fields Quick Reference

Field Type Phase Purpose
phase RenderPhase All Indicates the current rendering scenario.
quad BakedQuad All Contains face, tintIndex, icon, and four vertex coordinates/UVs.
world / x / y / z Block coordinates BLOCK null/0 for item phases.
block Block BLOCK null for item phases.
stack ItemStack ITEM null for block phases. Can read NBT/damage/enchantments for fine logic.
baselineBrightness int (final) All Default brightness computed by the renderer (neighbor light for blocks, full bright or GUI default for items).
skip bool All If set to true: this quad is discarded and the chain stops.
color int 0xRRGGBB All Color multiplier. Default 0xFFFFFF. Prefer using mulColor() to combine.
brightnessOverride int All Overrides brightness when ≥0; -1 falls back to baselineBrightness.
shade float All Directional light intensity (top 1.0 / side 0.8 / bottom 0.5). Extensions can override for flat shading.

2.3 Registration Timing

  • Can be registered during client init or postInit.
  • On first registration or first render, ModelRenderRegistry lazily installs the built-in TintRenderExtension.
  • The built-in extension always stays at the head of the chain; mod extensions are appended in registration order.

3. Built-in Extension: Tint + Overlay

Sources: TintRenderExtension
Convenience API: TintRegistry

3.1 JSON Model Syntax

Inspired by 1.13+ grass blocks (double elements: bottom dirt + side main texture, top overlay with tintindex):

{
  "elements": [
    {
      "from": [0, 0, 0], "to": [16, 16, 16],
      "faces": {
        "down":  { "texture": "#bottom" },
        "up":    { "texture": "#top",  "tintindex": 0 },
        "north": { "texture": "#side" },
        "south": { "texture": "#side" }
      }
    },
    {
      "from": [0, 0, 0], "to": [16, 16, 16],
      "faces": {
        "north": { "texture": "#overlay", "tintindex": 0 },
        "south": { "texture": "#overlay", "tintindex": 0 }
      }
    }
  ]
}

Any face with "tintindex" will be tinted by TintRenderExtension during rendering.

3.2 Default Behavior (Zero Configuration Works)

Scenario Default Source
Block (in world) block.colorMultiplier(world, x, y, z)
Item (GUI / held) If it is an ItemBlock, uses block.getRenderColor(damage)

Vanilla grass, leaves, water, lily pads, etc., already return biome colors in their own colorMultiplier — just add tintindex to the JSON and it works.

3.3 Custom Tinting

// Block side: use world + coordinates to decide color (biome/gradient/block state…)
TintRegistry.registerBlockTint(MyBlocks.ARCTIC_GRASS,
    (world, x, y, z, b, idx) -> 0xB8E0FF);

// Item side: use ItemStack to decide color (NBT / damage / enchantments)
TintRegistry.registerItemTint(MyItems.DYE_BAG,
    (stack, idx) -> stack.getTagCompound() != null
        ? stack.getTagCompound().getInteger("color")
        : 0xFFFFFF);

4. Future Extension Examples

All of the following can be achieved by simply writing and registering an IModelRenderExtension — no changes to CatFrame core needed.

4.1 Shadow (Brightness Override)

ModelRenderRegistry.register(ctx -> {
    if (ctx.phase != RenderPhase.BLOCK_WORLD) return;
    if (ctx.quad.face == EnumFacing.UP && ctx.world.getBlock(ctx.x, ctx.y + 1, ctx.z) == Blocks.snow_layer) {
        // Darken top face when covered by snow
        ctx.brightnessOverride = Math.max(0, ctx.baselineBrightness - 0x100010);
    }
});

4.2 Flat Shading (Disable Directional Shading)

ModelRenderRegistry.register(ctx -> {
    if (ctx.block instanceof BlockNeon) ctx.shade = 1.0f;
});

4.3 Face Culling (Hide Faces Based on Neighbor Blocks)

ModelRenderRegistry.register(ctx -> {
    if (ctx.phase != RenderPhase.BLOCK_WORLD || ctx.quad.face == null) return;
    EnumFacing f = ctx.quad.face;
    int nx = ctx.x + f.getFrontOffsetX();
    int ny = ctx.y + f.getFrontOffsetY();
    int nz = ctx.z + f.getFrontOffsetZ();
    if (ctx.world.getBlock(nx, ny, nz) == ctx.block) ctx.skip = true; // Don't render face adjacent to same block
});

4.4 Self-illuminating Item (Full Bright When Held)

ModelRenderRegistry.register(ctx -> {
    if (ctx.phase == RenderPhase.ITEM_HAND && ctx.stack != null && ctx.stack.getItem() == MyItems.TORCH) {
        ctx.brightnessOverride = 0xF000F0;
    }
});

5. Design Constraints & Hints

  1. High call frequency: The extension chain is invoked per quad per block per frame. Avoid expensive operations like reflection, I/O, string concatenation inside apply.
  2. Order-sensitive: Extensions registered later see modifications made by earlier extensions. To "take exclusive control", set skip = true to terminate the chain immediately.
  3. Do not modify quad geometry: BakedQuad is shared across multiple render caches; modifying its fields will pollute other renders. To change vertices, read quad.vx/vy/vz and output to Tessellator — but the current extension interface intentionally does not expose vertex writing to prevent misuse.
  4. Block/item duality: Vanilla RenderBlocks uses the world path — as long as a block has a JSON model, it will also display correctly in the inventory with biome/default tinting.
  5. Unregistering: Call ModelRenderRegistry.unregister(yourExt) on mod hot-reload or unload for cleanup.

6. File Index

Path Description
src/main/java/.../model/render/IModelRenderExtension.java Extension interface
src/main/java/.../model/render/RenderContext.java Context object
src/main/java/.../model/render/RenderPhase.java Rendering phase enum
src/main/java/.../model/render/ModelRenderRegistry.java Registry
src/main/java/.../model/render/tint/TintRenderExtension.java Built-in: tintindex processing
src/main/java/.../model/render/tint/TintRegistry.java Convenience API for tint registration
src/main/java/.../model/render/tint/IBlockTintProvider.java Tint interface (block side)
src/main/java/.../model/render/tint/IItemTintProvider.java Tint interface (item side)
src/main/java/.../model/VanillaModelManager.java#renderQuads / drawItemQuads Two call points for the extension chain

Clone this wiki locally