diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/ArmorRenderer.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/ArmorRenderer.java new file mode 100644 index 000000000..936a61dbe --- /dev/null +++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/ArmorRenderer.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.rendering.v1; + +import net.minecraft.client.model.Model; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemConvertible; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.impl.client.rendering.ArmorRendererRegistryImpl; + +/** + * Armor renderers render worn armor items with custom code. + * They may be used to render armor with special models or effects. + * + *

The renderers are registered with {@link net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer#register(ArmorRenderer, ItemConvertible...)}. + */ +@Environment(EnvType.CLIENT) +@FunctionalInterface +public interface ArmorRenderer { + /** + * Registers the armor renderer for the specified items. + * @param renderer the renderer + * @param items the items + * @throws IllegalArgumentException if an item already has a registered armor renderer + * @throws NullPointerException if either an item or the renderer is null + */ + static void register(ArmorRenderer renderer, ItemConvertible... items) { + ArmorRendererRegistryImpl.register(renderer, items); + } + + /** + * Helper method for rendering a specific armor model, comes after setting visibility. + * + *

This primarily handles applying glint and the correct {@link RenderLayer} + * @param matrices the matrix stack + * @param vertexConsumers the vertex consumer provider + * @param light packed lightmap coordinates + * @param stack the item stack of the armor item + * @param model the model to be rendered + * @param texture the texture to be applied + */ + static void renderPart(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, ItemStack stack, Model model, Identifier texture) { + VertexConsumer vertexConsumer = ItemRenderer.getArmorGlintConsumer(vertexConsumers, RenderLayer.getArmorCutoutNoCull(texture), false, stack.hasGlint()); + model.render(matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); + } + + /** + * Renders an armor part. + * + * @param matrices the matrix stack + * @param vertexConsumers the vertex consumer provider + * @param stack the item stack of the armor item + * @param entity the entity wearing the armor item + * @param slot the equipment slot in which the armor stack is worn + * @param light packed lightmap coordinates + * @param contextModel the model provided by {@link FeatureRenderer#getContextModel()} + */ + void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, ItemStack stack, LivingEntity entity, EquipmentSlot slot, int light, BipedEntityModel contextModel); +} diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorRendererRegistryImpl.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorRendererRegistryImpl.java new file mode 100644 index 000000000..206547d77 --- /dev/null +++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorRendererRegistryImpl.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.rendering; + +import java.util.HashMap; +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.item.ItemConvertible; +import net.minecraft.item.Item; +import net.minecraft.util.registry.Registry; + +import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer; + +public class ArmorRendererRegistryImpl { + private static final HashMap RENDERERS = new HashMap<>(); + + public static void register(ArmorRenderer renderer, ItemConvertible... items) { + Objects.requireNonNull(renderer, "renderer is null"); + + if (items.length == 0) { + throw new IllegalArgumentException("Armor renderer registered for no item"); + } + + for (ItemConvertible item : items) { + Objects.requireNonNull(item.asItem(), "armor item is null"); + + if (RENDERERS.putIfAbsent(item.asItem(), renderer) != null) { + throw new IllegalArgumentException("Custom armor renderer already exists for " + Registry.ITEM.getId(item.asItem())); + } + } + } + + @Nullable + public static ArmorRenderer get(Item item) { + return RENDERERS.get(item); + } +} diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinArmorFeatureRenderer.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinArmorFeatureRenderer.java new file mode 100644 index 000000000..3e4dc6519 --- /dev/null +++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinArmorFeatureRenderer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.rendering; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; + +import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer; +import net.fabricmc.fabric.impl.client.rendering.ArmorRendererRegistryImpl; + +@Mixin(ArmorFeatureRenderer.class) +public abstract class MixinArmorFeatureRenderer extends FeatureRenderer> { + private MixinArmorFeatureRenderer(FeatureRendererContext> context) { + super(context); + } + + @Inject(method = "renderArmor", at = @At("HEAD"), cancellable = true) + private void renderArmor(MatrixStack matrices, VertexConsumerProvider vertexConsumers, LivingEntity entity, EquipmentSlot armorSlot, int light, BipedEntityModel model, CallbackInfo ci) { + ItemStack stack = entity.getEquippedStack(armorSlot); + ArmorRenderer renderer = ArmorRendererRegistryImpl.get(stack.getItem()); + + if (renderer != null) { + renderer.render(matrices, vertexConsumers, stack, entity, armorSlot, light, getContextModel()); + ci.cancel(); + } + } +} diff --git a/fabric-rendering-v1/src/main/resources/fabric-rendering-v1.mixins.json b/fabric-rendering-v1/src/main/resources/fabric-rendering-v1.mixins.json index 706fed489..e794041a0 100644 --- a/fabric-rendering-v1/src/main/resources/fabric-rendering-v1.mixins.json +++ b/fabric-rendering-v1/src/main/resources/fabric-rendering-v1.mixins.json @@ -3,6 +3,7 @@ "package": "net.fabricmc.fabric.mixin.client.rendering", "compatibilityLevel": "JAVA_16", "client": [ + "MixinArmorFeatureRenderer", "MixinBlockColorMap", "MixinBuiltinModelItemRenderer", "MixinInGameHud", diff --git a/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/ArmorRenderingTests.java b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/ArmorRenderingTests.java new file mode 100644 index 000000000..89149acda --- /dev/null +++ b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/ArmorRenderingTests.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.rendering.client; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.model.EntityModelLayers; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.Items; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer; + +public class ArmorRenderingTests implements ClientModInitializer { + private BipedEntityModel armorModel; + private final Identifier texture = new Identifier("textures/block/dirt.png"); + + // Renders a biped model with dirt texture, replacing diamond helmet and diamond chest plate rendering + @Override + public void onInitializeClient() { + ArmorRenderer.register((matrices, vertexConsumers, stack, entity, slot, light, model) -> { + if (armorModel == null) { + armorModel = new BipedEntityModel<>(MinecraftClient.getInstance().getEntityModelLoader().getModelPart(EntityModelLayers.PLAYER_OUTER_ARMOR)); + } + + model.setAttributes(armorModel); + armorModel.setVisible(false); + armorModel.body.visible = slot == EquipmentSlot.CHEST; + armorModel.leftArm.visible = slot == EquipmentSlot.CHEST; + armorModel.rightArm.visible = slot == EquipmentSlot.CHEST; + armorModel.head.visible = slot == EquipmentSlot.HEAD; + ArmorRenderer.renderPart(matrices, vertexConsumers, light, stack, armorModel, texture); + }, Items.DIAMOND_HELMET, Items.DIAMOND_CHESTPLATE); + } +} diff --git a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json index 5f8ecc40d..c1eb0a5ed 100644 --- a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json @@ -10,7 +10,8 @@ }, "entrypoints": { "client": [ - "net.fabricmc.fabric.test.rendering.client.WorldRenderEventsTests" + "net.fabricmc.fabric.test.rendering.client.WorldRenderEventsTests", + "net.fabricmc.fabric.test.rendering.client.ArmorRenderingTests" ] } }