Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Model Generation, data generation for custom blocks via blockstates. #268

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
cc75667
Created example ModelProvider
Fellteros Jan 17, 2025
dbd321c
Registered ModelProvider
Fellteros Jan 17, 2025
0215b1c
Added an example block
Fellteros Jan 17, 2025
f9bfaee
Revert since I created the files in the wrong version. srry bout that
Fellteros Jan 17, 2025
f63d2bf
Added the example ModelProvider and registered it
Fellteros Jan 17, 2025
a044921
created the actual page and added it to the sidebar
Fellteros Jan 17, 2025
eaadf7c
Added Simple Cube All, Singletons and Block Texture Pool chapters
Fellteros Jan 18, 2025
caa5ce7
Added the Model Generation translation
Fellteros Jan 18, 2025
8e33244
Added textures to Ruby Block, Door, Trapdoor and Steel Block
Fellteros Jan 18, 2025
9954500
Added textures to Ruby Door and Pipe Block
Fellteros Jan 18, 2025
2e71383
Added page for item model generation. Feel free to complete it, I won't.
Fellteros Jan 18, 2025
e1dd6e1
Added the Item Model Generation Page, renamed models.md to block-mode…
Fellteros Jan 18, 2025
40f0bb0
Added translations for all blocks from Model Generation tutorial.
Fellteros Jan 18, 2025
521d207
Added needed methods
Fellteros Jan 18, 2025
58fe6b9
Added a new helper method for easier addition of Items
Fellteros Jan 18, 2025
c485173
Added example blocks and a new helper method for Model Generation
Fellteros Jan 18, 2025
deeef7c
Put all textures into one atlas
Fellteros Jan 18, 2025
4574ef3
Completed the tutorial, added links and sources
Fellteros Jan 18, 2025
9e08adb
Added translations for all blocks from Block Model Generation
Fellteros Jan 18, 2025
2af0474
Added translations for block-models.md and item-models.md
Fellteros Jan 18, 2025
abb2c06
Created an example block for the custom datagen method implementation
Fellteros Jan 18, 2025
3748daa
blockstates, block models and item models of all blocks from Block Mo…
Fellteros Jan 18, 2025
1b9928f
blockstates, block models and item models of all blocks from Block Mo…
Fellteros Jan 18, 2025
db83bf0
Merge remote-tracking branch 'origin/main'
Fellteros Jan 18, 2025
1c4ffe1
Deleted item-models.md as it is redundant.
Fellteros Jan 19, 2025
6c740c5
Removed item-models.md page, as it is redundant
Fellteros Jan 19, 2025
c49b770
Removed the getStateForNeighborUpdate method as it is redundant.
Fellteros Jan 19, 2025
79e6751
Removed the helper methods as they are redundant.
Fellteros Jan 19, 2025
2ef8bdf
Optimized imports
Fellteros Jan 19, 2025
48a7e5e
renamed Identifiers for better understanding
Fellteros Jan 19, 2025
c52d367
Made changes according to natri0's suggestions (thx a lot)
Fellteros Jan 19, 2025
bf251a2
Made changes according to IMB11's requests
Fellteros Jan 19, 2025
066f319
Reformatted the RUBY_FAMILY
Fellteros Jan 19, 2025
8bbf9cc
Deleted as it isn't used anymore
Fellteros Jan 19, 2025
e26528e
Added previews and files for download into the assets folder
Fellteros Jan 19, 2025
1458660
Fixed a few typos
Fellteros Jan 19, 2025
109dafd
(Hopefully) fixed all markdown-lint issues in block-models.md
Fellteros Jan 20, 2025
fdce0da
Fixed a few typos and optimized imports
Fellteros Jan 19, 2025
68b5276
Merge remote-tracking branch 'origin/main'
Fellteros Jan 20, 2025
f291c76
Checkstyle fixes
Fellteros Jan 20, 2025
bea15b9
Added item texture for Ruby Door, added it to the textures .zip folde…
Fellteros Jan 20, 2025
55684d1
Changed according to its-miroma's suggestions
Fellteros Jan 20, 2025
69c26df
Capitalized the PREREQUISITES title in block-models.md
Fellteros Jan 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .vitepress/sidebars/develop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ export default [
{
text: "develop.dataGeneration.lootTables",
link: "/develop/data-generation/loot-tables"
}
},
{
text: "develop.dataGeneration.blockModels",
link: "/develop/data-generation/block-models"
}
]
},
{
Expand Down
258 changes: 258 additions & 0 deletions develop/data-generation/block-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
---
title: Block Model Generation
description: A guide to generating block models & blockstates via datagen.
authors:
- Fellteros
- natri0
- IMB11
---

# Block Model Generation {#model-generation}
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

::: info Prerequisites
its-miroma marked this conversation as resolved.
Show resolved Hide resolved
Make sure you've completed the [datagen setup](./setup) process first.
:::

## Setup {#setup}

First, we will need to create our ModelProvider. Create a class that `extends FabricModelProvider`. Implement both abstract methods, `generateBlockStateModels` & `generateItemModels`.
Lastly, create constructor matching super.
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

@[code lang=java transcludeWith=:::datagen-model:provider](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

Register this class in your `DataGeneratorEntrypoint` within the `onInitializeDataGenerator` method.

## Blockstates and Block Models {#blockstates-and-block-models}

```java
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}
```

For block models, we will primarily be focusing on the `generateBlockStateModels` method. Notice the parameter `BlockStateModelGenerator blockStateModelGenerator` - this object will be responsible for generating all the JSON files.
Here are some handy examples you can use to generate your desired models:

### Simple Cube All {#simple-cube-all}

@[code lang=java transcludeWith=:::datagen-model:cube-all](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

This is the most commonly used function. It generates a JSON model file for a normal `cube_all` block model. One texture is used for all six sides, in this case we use `steel_block`.

@[code](@/reference/latest/src/main/generated/assets/fabric-docs-reference/models/block/steel_block.json)

It also generates a blockstate JSON file. Since we have no blockstate properties (e.g. Axis, Facing, ...), one variant is sufficient, and is used every time the block is placed.

@[code](@/reference/latest/src/main/generated/assets/fabric-docs-reference/blockstates/steel_block.json)

<DownloadEntry visualURL="/assets/develop/data-generation/block-model/steel_block_big.png" downloadURL="/assets/develop/data-generation/block-model/steel_block.png">Steel Block</DownloadEntry>

### Singletons {#singletons}

The `registerSingleton` method provides JSON model files based on the `TexturedModel` you pass in and a single blockstate variant.

@[code lang=java transcludeWith=:::datagen-model:cube-top-for-ends](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

This method will generate models for a normal cube, that uses the texture file `pipe_block` for the sides and the texture file `pipe_block_top` for the top and bottom sides.

@[code](@/reference/latest/src/main/generated/assets/fabric-docs-reference/models/block/pipe_block.json)

:::tip
If you're stuck choosing which `TextureModel` you should use, open the `TexturedModel` class and look at the [`TextureMaps`](#using-texture-map)!
:::

<DownloadEntry visualURL="/assets/develop/data-generation/block-model/pipe_block_textures_big.png" downloadURL="/assets/develop/data-generation/block-model/pipe_block_textures.zip">Pipe Block</DownloadEntry>

### Block Texture Pool {#block-texture-pool}

@[code lang=java transcludeWith=:::datagen-model:block-texture-pool-normal](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

Another useful method is `registerCubeAllModelTexturePool`. Define the textures by passing in the "base block", and then append the "children", which will have the same textures.
its-miroma marked this conversation as resolved.
Show resolved Hide resolved
In this case, we passed in the `RUBY_BLOCK`, so the stairs, slab and fence will use the `RUBY_BLOCK` texture.
**_It will also generate a [simple cube all JSON model](#simple-cube-all) for the "base block" to ensure that it has a block model._**
Be aware of this, if you're changing block model of this particular block, as it will result in en error.
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

You can also append a `BlockFamily`, which will generate models for all of its "children".

@[code lang=java transcludeWith=:::datagen-model:family-declaration](@/reference/latest/src/main/java/com/example/docs/block/ModBlocks.java)

@[code lang=java transcludeWith=:::datagen-model:block-texture-pool-family](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

<DownloadEntry visualURL="/assets/develop/data-generation/block-model/ruby_block_big.png" downloadURL="/assets/develop/data-generation/block-model/ruby_block.png">Ruby Block</DownloadEntry>

### Doors and Trapdoors {#doors-and-trapdoors}

@[code lang=java transcludeWith=:::datagen-model:door-and-trapdoor](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

Doors and trapdoors are a little different. Here, you have to make three new textures - two for the door, and one for the trapdoor.

1. **The door**. It has two parts - the upper half and the lower half. **Each needs its own texture:** in this case `ruby_door_top` for the upper half and `ruby_door_bottom` for the lower.
The `registerDoor()` method will create models for all orientations of the door, both open and closed.
**You also need an item texture!** Put it in `assets/<mod_id>/textures/item/` folder.
2. **The trapdoor**. Here, you need only one texture, in this case named `ruby_trapdoor`. It will be used for all sides.
Since `TrapdoorBlock` has a `FACING` property, you can use the commented out method to generate model files with rotated textures = the trapdoor will be "orientable".
Otherwise, it will look the same no matter the direction it's facing.
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

<DownloadEntry visualURL="/assets/develop/data-generation/block-model/ruby_door_trapdoor_big.png" downloadURL="/assets/develop/data-generation/block-model/ruby_door_trapdoor_textures.zip">Ruby Door and Trapdoor</DownloadEntry>

## Custom Block Models and Datagen Methods {#custom-models-and-methods}
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

In this section, we'll create the models for a **vertical slab block** with Oak Log textures => _Vertical Oak Log Slab_.
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

_Points 2. - 6. are declared in an inner static helper class called `CustomBlockStateModelGenerator`._

### Custom Block Class {#custom-block-class}

Create a `VerticalSlab` block with a `FACING` property and a `SINGLE` boolean property, like in the [Block States](../blocks/block-states) tutorial. `SINGLE` will indicate if there are both slabs.
Then you should override `getOutlineShape` and `getCollisionShape`, so that the outline is rendered correctly, and the block has the correct collision shape.

@[code lang=java transcludeWith=:::datagen-model-custom:voxels](@/reference/latest/src/main/java/com/example/docs/block/custom/VerticalSlabBlock.java)

@[code lang=java transcludeWith=:::datagen-model-custom:collision](@/reference/latest/src/main/java/com/example/docs/block/custom/VerticalSlabBlock.java)

Also override the `canReplace()` method, otherwise you couldn't make the slab a full block.

@[code lang=java transcludeWith=:::datagen-model-custom:replace](@/reference/latest/src/main/java/com/example/docs/block/custom/VerticalSlabBlock.java)

And you're done! You can now test the block out and place it in game.

### Parent Block Model {#parent-block-model}

Now, let's create a parent block model. It will determine the size, position in hand or other slots and the `x` and `y` coordinates of the texture.
I highly recommend using [Blockbench](https://www.blockbench.net/) for this, as making it manually is a really tedious process. It should look something like this:

```json
{
"parent": "minecraft:block/block",
"textures": {
"particle": "#side"
},
"display": {
"gui": {
"rotation": [ 30, -135, 0 ],
"translation": [ -1.5, 0.75, 0],
"scale":[ 0.625, 0.625, 0.625 ]
},
"firstperson_righthand": {
"rotation": [ 0, -45, 0 ],
"translation": [ 0, 2, 0],
"scale":[ 0.375, 0.375, 0.375 ]
},
"firstperson_lefthand": {
"rotation": [ 0, 315, 0 ],
"translation": [ 0, 2, 0],
"scale":[ 0.375, 0.375, 0.375 ]
},
"thirdperson_righthand": {
"rotation": [ 75, -45, 0 ],
"translation": [ 0, 0, 2],
"scale":[ 0.375, 0.375, 0.375 ]
},
"thirdperson_lefthand": {
"rotation": [ 75, 315, 0 ],
"translation": [ 0, 0, 2],
"scale":[ 0.375, 0.375, 0.375 ]
}
},
"elements": [
{ "from": [ 0, 0, 0 ],
"to": [ 16, 16, 8 ],
"faces": {
"down": { "uv": [ 0, 8, 16, 16 ], "texture": "#bottom", "cullface": "down", "tintindex": 0 },
"up": { "uv": [ 0, 0, 16, 8 ], "texture": "#top", "cullface": "up", "tintindex": 0 },
"north": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "north", "tintindex": 0 },
"south": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "tintindex": 0 },
"west": { "uv": [ 0, 0, 8, 16 ], "texture": "#side", "cullface": "west", "tintindex": 0 },
"east": { "uv": [ 8, 0, 16, 16 ], "texture": "#side", "cullface": "east", "tintindex": 0 }
}
}
]
}
```
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

See [how blockstates are formatted](https://minecraft.wiki/w/Blockstates_definition_format) for more information.
Notice the `#bottom`, `#top`, `#side` keywords. They act as variables that can be set by models that have this one as a parent:

```json
{
"parent": "minecraft:block/cube_bottom_top",
"textures": {
"bottom": "minecraft:block/sandstone_bottom",
"side": "minecraft:block/sandstone",
"top": "minecraft:block/sandstone_top"
}
}
```

The `bottom` value will replace the `#bottom` placeholder and so on. **Put it in the `resources/assets/<mod_id>/models/block/` folder.**
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

### Custom Model {#custom-model}

Another thing we will need is an instance of the `Model` class. It will represent the actual [parent block model](#parent-block-model) inside our mod.

@[code lang=java transcludeWith=:::datagen-model-custom:model](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

The `block()` method creates a new `Model`, pointing to the `vertical_slab.json` file inside the `resources/assets/<mod_id>/models/block/` folder.
its-miroma marked this conversation as resolved.
Show resolved Hide resolved
The `TextureKey`s represent the "placeholders" (`#bottom`, `#top`, ...) as an Object.

### Using Texture Map {#using-texture-map}

What does `TextureMap` do? It actually provides the Identifiers that point to the textures. It technically behaves like a normal map - you associate a `TextureKey` (Key) with an `Identifier` (Value).
You can:

1. Use the Vanilla ones, e.g. `TextureMap.all()`(associates all TextureKeys with the same Identifier) or other.
2. Create a new one by creating a new instance and then using `.put()` to associate keys with values.
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

::: tip
`TextureMap.all()` associates all TextureKeys with the same Identifier, no matter how many of them there are!
:::

Since we want to use the Oak Log textures, but have the ``BOTTOM``, ``TOP`` and ``SIDE`` ``TextureKey``s, we need to create a new one.

@[code lang=java transcludeWith=:::datagen-model-custom:texture-map](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

The ``bottom`` and ``top`` faces will use `oak_log_top.png`, the sides will use `oak_log.png`.

::: warning
All `TextureKey`s in the TextureMap **have to** match all `TextureKey`s in your parent block model!
:::

### Custom BlockStateSupplier Method {#custom-supplier-method}
its-miroma marked this conversation as resolved.
Show resolved Hide resolved

The `BlockStateSupplier` contains all blockstate variants, their rotation, and other options like uvlock.

@[code lang=java transcludeWith=:::datagen-model-custom:supplier](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

First, we create a new `VariantsBlockStateSupplier` using `VariantsBlockStateSupplier.create()`.
Then we create a new `BlockStateVariantMap` that contains parameters for all variants of the block, in this case `FACING` & `SINGLE`, and pass it into the `VariantsBlockStateSupplier`.
Specify which model and which transformations (uvlock, rotation) is used when using `.register()`.
For example:

- On the first line, the block is facing north, and is single => we use the model with no rotation.
- On the fourth line, the block is facing west, and is single => we rotate the model on the Y axis by 270°.
- On the sixth line, the block is facing east, but isn't single => it looks like a normal oak log => we don't have to rotate it.

### Custom Datagen Method {#custom-datagen-method}

The last step - creating an actual method you can call and that will generate the JSONs.
But what are the parameters for?

1. `BlockStateModelGenerator bsmg`, the same one that got passed into `generateBlockStateModels`.
2. `Block vertSlabBlock` is the block to which we will generate the JSONs.
3. `Block fullBlock` - is the model used when the `SINGLE` property is false = the slab block looks like a full block.
4. `TextureMap textures` defines the actual textures the model uses. See the [Using Texture Map](#using-texture-map) chapter.

@[code lang=java transcludeWith=:::datagen-model-custom:gen](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

First, we get the `Identifier` of the single slab model with `VERTICAL_SLAB.upload()`. Then we get the `Identifier` of the full block model with `ModelIds.getBlockModelId()`, and pass those two models into `createVerticalSlabBlockStates`.
The `BlockStateSupplier` gets passed into the `blockStateCollector`, so that the JSON files are actually generated.
Also, we create a model for the vertical slab item with `BlockStateModelGenerator.registerParentedItemModel()`.

And that is all! Now all that's left to do is to call our method in our `ModelProvider`:

@[code lang=java transcludeWith=:::datagen-model-custom:method-call](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceModelProvider.java)

## Sources and Links {#sources-and-links}

Other examples of using custom datagen methods have been created using [Vanilla+ Blocks](https://github.com/Fellteros/vanillablocksplus) and [Vanilla+ Verticals](https://github.com/Fellteros/vanillavsplus) mods.
You can also view the example tests in Fabric API and the Fabric docs reference mod for more information.
its-miroma marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {

pack.addProvider(FabricDocsReferenceInternalModelProvider::new);

pack.addProvider(FabricDocsReferenceModelProvider::new);

// :::datagen-setup:generator
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ public void generateTranslations(RegistryWrapper.WrapperLookup wrapperLookup, Tr
translationBuilder.add(ModBlocks.COUNTER_BLOCK.asItem(), "Counter Block");
translationBuilder.add(ModBlocks.PRISMARINE_LAMP.asItem(), "Prismarine Lamp");
translationBuilder.add(ModBlocks.ENGINE_BLOCK.asItem(), "Engine Block");

translationBuilder.add(ModBlocks.STEEL_BLOCK, "Steel Block");
translationBuilder.add(ModBlocks.PIPE_BLOCK, "Pipe Block");
translationBuilder.add(ModBlocks.RUBY_BLOCK, "Ruby Block");
translationBuilder.add(ModBlocks.RUBY_STAIRS, "Ruby Stairs");
translationBuilder.add(ModBlocks.RUBY_SLAB, "Ruby Slab");
translationBuilder.add(ModBlocks.RUBY_FENCE, "Ruby Fence");
translationBuilder.add(ModBlocks.RUBY_DOOR, "Ruby Door");
translationBuilder.add(ModBlocks.RUBY_TRAPDOOR, "Ruby Trapdoor");
translationBuilder.add(ModBlocks.VERTICAL_OAK_LOG_SLAB, "Vertical Oak Log Slab");
// :::datagen-translations:provider
}
}
Expand Down
Loading