Procedural models API

This commit is contained in:
Aleksey 2021-05-26 17:23:20 +03:00
parent cab98a75c2
commit 15d379996c
67 changed files with 1542 additions and 1 deletions

View file

@ -3,6 +3,7 @@ package ru.bclib;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.resources.ResourceLocation;
import ru.bclib.util.Logger;
import ru.bclib.world.surface.BCLSurfaceBuilders;
import ru.bclib.api.BCLibTags;
@ -24,4 +25,8 @@ public class BCLib implements ModInitializer {
public static boolean isClient() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
}
public static ResourceLocation makeID(String path) {
return new ResourceLocation(MOD_ID, path);
}
}

View file

@ -0,0 +1,61 @@
package ru.bclib.client.models;
import net.minecraft.resources.ResourceLocation;
import ru.bclib.BCLib;
public class BasePatterns {
//Block Models
public final static ResourceLocation BLOCK_EMPTY = BCLib.makeID("patterns/block/empty.json");
public final static ResourceLocation BLOCK_BASE = BCLib.makeID("patterns/block/block.json");
public final static ResourceLocation BLOCK_SIDED = BCLib.makeID("patterns/block/block_sided.json");
public final static ResourceLocation BLOCK_BOTTOM_TOP = BCLib.makeID("patterns/block/block_bottom_top.json");
public final static ResourceLocation BLOCK_SLAB = BCLib.makeID("patterns/block/slab.json");
public final static ResourceLocation BLOCK_STAIR = BCLib.makeID("patterns/block/stairs.json");
public final static ResourceLocation BLOCK_STAIR_INNER = BCLib.makeID("patterns/block/stairs_inner.json");
public final static ResourceLocation BLOCK_STAIR_OUTER = BCLib.makeID("patterns/block/stairs_outer.json");
public final static ResourceLocation BLOCK_WALL_POST = BCLib.makeID("patterns/block/wall_post.json");
public final static ResourceLocation BLOCK_WALL_SIDE = BCLib.makeID("patterns/block/wall_side.json");
public final static ResourceLocation BLOCK_WALL_SIDE_TALL = BCLib.makeID("patterns/block/wall_side_tall.json");
public final static ResourceLocation BLOCK_FENCE_POST = BCLib.makeID("patterns/block/fence_post.json");
public final static ResourceLocation BLOCK_FENCE_SIDE = BCLib.makeID("patterns/block/fence_side.json");
public final static ResourceLocation BLOCK_BUTTON = BCLib.makeID("patterns/block/button.json");
public final static ResourceLocation BLOCK_BUTTON_PRESSED = BCLib.makeID("patterns/block/button_pressed.json");
public final static ResourceLocation BLOCK_PILLAR = BCLib.makeID("patterns/block/pillar.json");
public final static ResourceLocation BLOCK_PLATE_UP = BCLib.makeID("patterns/block/pressure_plate_up.json");
public final static ResourceLocation BLOCK_PLATE_DOWN = BCLib.makeID("patterns/block/pressure_plate_down.json");
public final static ResourceLocation BLOCK_DOOR_TOP = BCLib.makeID("patterns/block/door_top.json");
public final static ResourceLocation BLOCK_DOOR_TOP_HINGE = BCLib.makeID("patterns/block/door_top_hinge.json");
public final static ResourceLocation BLOCK_DOOR_BOTTOM = BCLib.makeID("patterns/block/door_bottom.json");
public final static ResourceLocation BLOCK_DOOR_BOTTOM_HINGE = BCLib.makeID("patterns/block/door_bottom_hinge.json");
public final static ResourceLocation BLOCK_CROSS = BCLib.makeID("patterns/block/cross.json");
public final static ResourceLocation BLOCK_CROSS_SHADED = BCLib.makeID("patterns/block/cross_shaded.json");
public final static ResourceLocation BLOCK_GATE_CLOSED = BCLib.makeID("patterns/block/fence_gate_closed.json");
public final static ResourceLocation BLOCK_GATE_CLOSED_WALL = BCLib.makeID("patterns/block/wall_gate_closed.json");
public final static ResourceLocation BLOCK_GATE_OPEN = BCLib.makeID("patterns/block/fence_gate_open.json");
public final static ResourceLocation BLOCK_GATE_OPEN_WALL = BCLib.makeID("patterns/block/wall_gate_open.json");
public final static ResourceLocation BLOCK_TRAPDOOR = BCLib.makeID("patterns/block/trapdoor.json");
public final static ResourceLocation BLOCK_LADDER = BCLib.makeID("patterns/block/ladder.json");
public final static ResourceLocation BLOCK_BARREL_OPEN = BCLib.makeID("patterns/block/barrel_open.json");
public final static ResourceLocation BLOCK_BOOKSHELF = BCLib.makeID("patterns/block/bookshelf.json");
public final static ResourceLocation BLOCK_COMPOSTER = BCLib.makeID("patterns/block/composter.json");
public final static ResourceLocation BLOCK_COLORED = BCLib.makeID("patterns/block/block_colored.json");
public final static ResourceLocation BLOCK_BARS_POST = BCLib.makeID("patterns/block/bars_post.json");
public final static ResourceLocation BLOCK_BARS_SIDE = BCLib.makeID("patterns/block/bars_side.json");
public final static ResourceLocation BLOCK_ANVIL = BCLib.makeID("patterns/block/anvil.json");
public final static ResourceLocation BLOCK_CHAIN = BCLib.makeID("patterns/block/chain.json");
public final static ResourceLocation BLOCK_FURNACE = BCLib.makeID("patterns/block/furnace.json");
public final static ResourceLocation BLOCK_FURNACE_LIT = BCLib.makeID("patterns/block/furnace_glow.json");
public final static ResourceLocation BLOCK_TOP_SIDE_BOTTOM = BCLib.makeID("patterns/block/top_side_bottom.json");
public final static ResourceLocation BLOCK_PATH = BCLib.makeID("patterns/block/path.json");
//Item Models
public final static ResourceLocation ITEM_WALL = BCLib.makeID("patterns/item/pattern_wall.json");
public final static ResourceLocation ITEM_FENCE = BCLib.makeID("patterns/item/pattern_fence.json");
public final static ResourceLocation ITEM_BUTTON = BCLib.makeID("patterns/item/pattern_button.json");
public final static ResourceLocation ITEM_CHEST = BCLib.makeID("patterns/item/pattern_chest.json");
public final static ResourceLocation ITEM_BLOCK = BCLib.makeID("patterns/item/pattern_block_item.json");
public final static ResourceLocation ITEM_GENERATED = BCLib.makeID("patterns/item/pattern_item_generated.json");
public final static ResourceLocation ITEM_HANDHELD = BCLib.makeID("patterns/item/pattern_item_handheld.json");
public final static ResourceLocation ITEM_SPAWN_EGG = BCLib.makeID("patterns/item/pattern_item_spawn_egg.json");
}

View file

@ -0,0 +1,40 @@
package ru.bclib.client.models;
import static net.minecraft.client.resources.model.ModelBakery.MISSING_MODEL_LOCATION;
import java.util.Map;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.state.BlockState;
import ru.bclib.BCLib;
public interface BlockModelProvider extends ItemModelProvider {
default @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
Optional<String> pattern = PatternsHelper.createBlockSimple(resourceLocation);
return ModelsHelper.fromPattern(pattern);
}
default UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
registerBlockModel(stateId, modelId, blockState, modelCache);
return ModelsHelper.createBlockSimple(modelId);
}
default void registerBlockModel(ResourceLocation stateId, ResourceLocation modelId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
if (!modelCache.containsKey(modelId)) {
BlockModel model = getBlockModel(stateId, blockState);
if (model != null) {
model.name = modelId.toString();
modelCache.put(modelId, model);
} else {
BCLib.LOGGER.warning("Error loading model: {}", modelId);
modelCache.put(modelId, modelCache.get(MISSING_MODEL_LOCATION));
}
}
}
}

View file

@ -0,0 +1,10 @@
package ru.bclib.client.models;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.resources.ResourceLocation;
public interface ItemModelProvider {
default BlockModel getItemModel(ResourceLocation resourceLocation) {
return ModelsHelper.createItemModel(resourceLocation);
}
}

View file

@ -0,0 +1,147 @@
package ru.bclib.client.models;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import com.google.common.collect.Lists;
import com.mojang.math.Transformation;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.MultiVariant;
import net.minecraft.client.renderer.block.model.Variant;
import net.minecraft.client.renderer.block.model.multipart.Condition;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import net.minecraft.client.renderer.block.model.multipart.Selector;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
@Environment(EnvType.CLIENT)
public class ModelsHelper {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static BlockModel fromPattern(Optional<String> pattern) {
return pattern.map(BlockModel::fromString).orElse(null);
}
public static BlockModel createItemModel(ResourceLocation resourceLocation) {
return fromPattern(PatternsHelper.createItemGenerated(resourceLocation));
}
public static BlockModel createHandheldItem(ResourceLocation resourceLocation) {
return fromPattern(PatternsHelper.createItemHandheld(resourceLocation));
}
public static BlockModel createBlockItem(ResourceLocation resourceLocation) {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_BLOCK, resourceLocation);
return fromPattern(pattern);
}
public static BlockModel createBlockEmpty(ResourceLocation resourceLocation) {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_EMPTY, resourceLocation);
return fromPattern(pattern);
}
public static MultiVariant createMultiVariant(ResourceLocation resourceLocation, Transformation transform, boolean uvLock) {
Variant variant = new Variant(resourceLocation, transform, uvLock, 1);
return new MultiVariant(Lists.newArrayList(variant));
}
public static MultiVariant createBlockSimple(ResourceLocation resourceLocation) {
return createMultiVariant(resourceLocation, Transformation.identity(), false);
}
public static MultiVariant createFacingModel(ResourceLocation resourceLocation, Direction facing, boolean uvLock, boolean inverted) {
if (inverted) {
facing = facing.getOpposite();
}
BlockModelRotation rotation = BlockModelRotation.by(0, (int) facing.toYRot());
return createMultiVariant(resourceLocation, rotation.getRotation(), uvLock);
}
public static MultiVariant createRotatedModel(ResourceLocation resourceLocation, Direction.Axis axis) {
BlockModelRotation rotation = BlockModelRotation.X0_Y0;
switch (axis) {
case X: rotation = BlockModelRotation.X90_Y90; break;
case Z: default: rotation = BlockModelRotation.X90_Y0; break;
}
return createMultiVariant(resourceLocation, rotation.getRotation(), false);
}
public static MultiVariant createRandomTopModel(ResourceLocation resourceLocation) {
return new MultiVariant(Lists.newArrayList(
new Variant(resourceLocation, Transformation.identity(), false, 1),
new Variant(resourceLocation, BlockModelRotation.X0_Y90.getRotation(), false, 1),
new Variant(resourceLocation, BlockModelRotation.X0_Y180.getRotation(), false, 1),
new Variant(resourceLocation, BlockModelRotation.X0_Y270.getRotation(), false, 1)
));
}
public static class MultiPartBuilder {
private final static MultiPartBuilder BUILDER = new MultiPartBuilder();
public static MultiPartBuilder create(StateDefinition<Block, BlockState> stateDefinition) {
BUILDER.stateDefinition = stateDefinition;
BUILDER.modelParts.clear();
return BUILDER;
}
private final List<ModelPart> modelParts = Lists.newArrayList();
private StateDefinition<Block, BlockState> stateDefinition;
private MultiPartBuilder() {}
public ModelPart part(ResourceLocation modelId) {
return new ModelPart(modelId);
}
public MultiPart build() {
if (modelParts.size() > 0) {
List<Selector> selectors = Lists.newArrayList();
modelParts.forEach(modelPart -> {
MultiVariant variant = createMultiVariant(modelPart.modelId, modelPart.transform, modelPart.uvLock);
selectors.add(new Selector(modelPart.condition, variant));
});
modelParts.clear();
return new MultiPart(stateDefinition, selectors);
}
throw new IllegalStateException("At least one model part need to be created.");
}
public class ModelPart {
private final ResourceLocation modelId;
private Transformation transform = Transformation.identity();
private Condition condition = Condition.TRUE;
private boolean uvLock = false;
private ModelPart(ResourceLocation modelId) {
this.modelId = modelId;
}
public ModelPart setCondition(Function<BlockState, Boolean> condition) {
this.condition = stateDefinition -> condition::apply;
return this;
}
public ModelPart setTransformation(Transformation transform) {
this.transform = transform;
return this;
}
public ModelPart setUVLock(boolean value) {
this.uvLock = value;
return this;
}
public void add() {
modelParts.add(this);
}
}
}
}

View file

@ -0,0 +1,65 @@
package ru.bclib.client.models;
import com.google.common.collect.Maps;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class PatternsHelper {
public static Optional<String> createItemGenerated(ResourceLocation itemId) {
return createJson(BasePatterns.ITEM_GENERATED, itemId);
}
public static Optional<String> createItemHandheld(ResourceLocation itemId) {
return createJson(BasePatterns.ITEM_HANDHELD, itemId);
}
public static Optional<String> createBlockSimple(ResourceLocation blockId) {
return createJson(BasePatterns.BLOCK_BASE, blockId);
}
public static Optional<String> createBlockEmpty(ResourceLocation blockId) {
return createJson(BasePatterns.BLOCK_EMPTY, blockId);
}
public static Optional<String> createBlockPillar(ResourceLocation blockId) {
return createJson(BasePatterns.BLOCK_PILLAR, blockId);
}
public static Optional<String> createBlockBottomTop(ResourceLocation blockId) {
return createJson(BasePatterns.BLOCK_BOTTOM_TOP, blockId);
}
public static Optional<String> createBlockColored(ResourceLocation blockId) {
return createJson(BasePatterns.BLOCK_COLORED, blockId);
}
public static Optional<String> createJson(ResourceLocation patternId, ResourceLocation blockId) {
Map<String, String> textures = Maps.newHashMap();
textures.put("%modid%", blockId.getNamespace());
textures.put("%texture%", blockId.getPath());
return createJson(patternId, textures);
}
public static Optional<String> createJson(ResourceLocation patternId, Map<String, String> textures) {
ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
try (InputStream input = resourceManager.getResource(patternId).getInputStream()) {
String json = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))
.lines().collect(Collectors.joining());
for (Map.Entry<String, String> texture : textures.entrySet()) {
json = json.replace(texture.getKey(), texture.getValue());
}
return Optional.of(json);
} catch (Exception ex) {
return Optional.empty();
}
}
}

View file

@ -0,0 +1,107 @@
package ru.bclib.mixin.client;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import ru.bclib.BCLib;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ItemModelProvider;
@Mixin(ModelBakery.class)
public abstract class ModelBakeryMixin {
@Final
@Shadow
private ResourceManager resourceManager;
@Final
@Shadow
private Map<ResourceLocation, UnbakedModel> unbakedCache;
@Shadow
protected abstract void cacheAndQueueDependencies(ResourceLocation resourceLocation, UnbakedModel unbakedModel);
@Inject(method = "loadModel", at = @At("HEAD"), cancellable = true)
private void bclib_loadModels(ResourceLocation resourceLocation, CallbackInfo info) {
if (resourceLocation instanceof ModelResourceLocation) {
String modId = resourceLocation.getNamespace();
String path = resourceLocation.getPath();
ResourceLocation clearLoc = new ResourceLocation(modId, path);
ModelResourceLocation modelId = (ModelResourceLocation) resourceLocation;
if ("inventory".equals(modelId.getVariant())) {
ResourceLocation itemLoc = new ResourceLocation(modId, "item/" + path);
ResourceLocation itemModelLoc = new ResourceLocation(modId, "models/" + itemLoc.getPath() + ".json");
if (!resourceManager.hasResource(itemModelLoc)) {
Item item = Registry.ITEM.get(clearLoc);
ItemModelProvider modelProvider = null;
if (item instanceof ItemModelProvider) {
modelProvider = (ItemModelProvider) item;
} else if (item instanceof BlockItem) {
Block block = Registry.BLOCK.get(clearLoc);
if (block instanceof ItemModelProvider) {
modelProvider = (ItemModelProvider) block;
}
}
if (modelProvider != null) {
BlockModel model = modelProvider.getItemModel(clearLoc);
if (model != null) {
model.name = itemLoc.toString();
cacheAndQueueDependencies(modelId, model);
unbakedCache.put(itemLoc, model);
} else {
BCLib.LOGGER.warning("Error loading model: {}", itemLoc);
}
info.cancel();
}
}
} else {
ResourceLocation stateLoc = new ResourceLocation(modId, "blockstates/" + path + ".json");
if (!resourceManager.hasResource(stateLoc)) {
Block block = Registry.BLOCK.get(clearLoc);
if (block instanceof BlockModelProvider) {
List<BlockState> possibleStates = block.getStateDefinition().getPossibleStates();
Optional<BlockState> possibleState = possibleStates.stream()
.filter(state -> modelId.equals(BlockModelShaper.stateToModelLocation(clearLoc, state)))
.findFirst();
if (possibleState.isPresent()) {
UnbakedModel modelVariant = ((BlockModelProvider) block).getModelVariant(modelId, possibleState.get(), unbakedCache);
if (modelVariant != null) {
if (modelVariant instanceof MultiPart) {
possibleStates.forEach(state -> {
ResourceLocation stateId = BlockModelShaper.stateToModelLocation(clearLoc, state);
cacheAndQueueDependencies(stateId, modelVariant);
});
} else {
cacheAndQueueDependencies(modelId, modelVariant);
}
} else {
BCLib.LOGGER.warning("Error loading variant: {}", modelId);
}
info.cancel();
}
}
}
}
}
}
}