From f21864f86dca7213fe35d73007f64d0b12673bce Mon Sep 17 00:00:00 2001
From: shedaniel <daniel@shedaniel.me>
Date: Mon, 16 Nov 2020 03:07:24 +0800
Subject: [PATCH] fabric-rendering-v1: Custom Armor Model & Texture (#963)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Moving testmod id to rendering-v1

Signed-off-by: shedaniel <daniel@shedaniel.me>

Moving testmod to rendering-v1

Signed-off-by: shedaniel <daniel@shedaniel.me>

Reviews

Signed-off-by: shedaniel <daniel@shedaniel.me>

prefix the extensions with armor

Signed-off-by: shedaniel <daniel@shedaniel.me>

change name

Signed-off-by: shedaniel <daniel@shedaniel.me>

drop custom

Signed-off-by: shedaniel <daniel@shedaniel.me>

thing

Signed-off-by: shedaniel <daniel@shedaniel.me>

javadocs

Signed-off-by: shedaniel <daniel@shedaniel.me>

fix imports

Signed-off-by: shedaniel <daniel@shedaniel.me>

forgot to do asItem

Signed-off-by: shedaniel <daniel@shedaniel.me>

add null checks and convert to ItemConvertible

Signed-off-by: shedaniel <daniel@shedaniel.me>

fix license

Signed-off-by: shedaniel <daniel@shedaniel.me>

did thing

Signed-off-by: shedaniel <daniel@shedaniel.me>

it now compiles

Signed-off-by: shedaniel <daniel@shedaniel.me>

change to a registry

Signed-off-by: shedaniel <daniel@shedaniel.me>

add @Unique

Signed-off-by: shedaniel <daniel@shedaniel.me>

migrate to fabric-item-api-v1

Signed-off-by: shedaniel <daniel@shedaniel.me>

did some renaming and improvements

Signed-off-by: shedaniel <daniel@shedaniel.me>

don't need that

Signed-off-by: shedaniel <daniel@shedaniel.me>

armor

Signed-off-by: shedaniel <daniel@shedaniel.me>

* add license to CustomArmorTests

Signed-off-by: shedaniel <daniel@shedaniel.me>

* Add @Nullable annotations and fix compile

Signed-off-by: shedaniel <daniel@shedaniel.me>

* javadoc

Signed-off-by: shedaniel <daniel@shedaniel.me>

* Fix reviews

Signed-off-by: shedaniel <daniel@shedaniel.me>

* Update fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinArmorFeatureRenderer.java

Co-authored-by: Erlend Åmdal <erlend@aamdal.com>

* Add registerSimpleTexture
Pass through secondLayer and suffix
Use Identifier's over strings
Fix the test mod

* license fix

Co-authored-by: Erlend Åmdal <erlend@aamdal.com>
Co-authored-by: modmuss50 <modmuss50@gmail.com>
---
 fabric-rendering-v1/build.gradle              |   2 +-
 .../rendering/v1/ArmorRenderingRegistry.java  | 165 ++++++++++++++++++
 .../rendering/ArmorProviderExtensions.java    |  33 ++++
 .../rendering/ArmorRenderingRegistryImpl.java |  79 +++++++++
 .../rendering/MixinArmorFeatureRenderer.java  | 103 +++++++++++
 .../mixin/client/rendering/MixinItem.java     |  53 ++++++
 .../resources/fabric-rendering-v1.mixins.json |   2 +
 .../test/rendering/CustomArmorTests.java      |  45 +++++
 .../client/CustomArmorTestsClient.java        |  68 ++++++++
 .../textures/cube.png                         | Bin 0 -> 471 bytes
 .../textures/custom_texture.png               | Bin 0 -> 670 bytes
 .../armor/simple_textured_armor_layer_1.png   | Bin 0 -> 141 bytes
 .../src/testmod/resources/fabric.mod.json     |  19 ++
 13 files changed, 568 insertions(+), 1 deletion(-)
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/ArmorRenderingRegistry.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorProviderExtensions.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorRenderingRegistryImpl.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinArmorFeatureRenderer.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinItem.java
 create mode 100644 fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/CustomArmorTests.java
 create mode 100644 fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/CustomArmorTestsClient.java
 create mode 100644 fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/cube.png
 create mode 100644 fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/custom_texture.png
 create mode 100644 fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/models/armor/simple_textured_armor_layer_1.png
 create mode 100644 fabric-rendering-v1/src/testmod/resources/fabric.mod.json

diff --git a/fabric-rendering-v1/build.gradle b/fabric-rendering-v1/build.gradle
index ef0e58c3d..ac1ae6867 100644
--- a/fabric-rendering-v1/build.gradle
+++ b/fabric-rendering-v1/build.gradle
@@ -1,5 +1,5 @@
 archivesBaseName = "fabric-rendering-v1"
-version = getSubprojectVersion(project, "1.3.1")
+version = getSubprojectVersion(project, "1.4.0")
 
 dependencies {
 	compile project(path: ':fabric-api-base', configuration: 'dev')
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/ArmorRenderingRegistry.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/ArmorRenderingRegistry.java
new file mode 100644
index 000000000..82719276f
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/ArmorRenderingRegistry.java
@@ -0,0 +1,165 @@
+/*
+ * 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 java.util.Arrays;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.client.render.entity.model.BipedEntityModel;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.util.Identifier;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.impl.client.rendering.ArmorRenderingRegistryImpl;
+
+/**
+ * A class for registering custom armor models and textures for {@link Item}, to be provided by a {@link ModelProvider} or {@link TextureProvider}.
+ *
+ * <p>This can be used to replace existing vanilla armor models and textures conditionally, however each {@link Item}
+ * instance can only allow one {@link ModelProvider} or {@link TextureProvider} respectively, causing potential conflicts
+ * with other mods if you replace the model or texture for vanilla items. Consider using a separate item instead.</p>
+ *
+ * <p>A custom model would probably also require a custom texture to go along it, the model will use the vanilla texture if it is undefined.</p>
+ *
+ * <p>Since armor textures identifier in vanilla is hardcoded to be in the {@code minecraft} namespace, this registry can also be
+ * used to use a custom namespace if desired.</p>
+ */
+@Environment(EnvType.CLIENT)
+public final class ArmorRenderingRegistry {
+	private ArmorRenderingRegistry() {
+	}
+
+	/**
+	 * Registers a provider for custom armor models for an item.
+	 *
+	 * @param provider the provider for the model
+	 * @param items    the items to be registered for
+	 */
+	public static void registerModel(@Nullable ModelProvider provider, Item... items) {
+		registerModel(provider, Arrays.asList(items));
+	}
+
+	/**
+	 * Registers a provider for custom armor models for an item.
+	 *
+	 * @param provider the provider for the model
+	 * @param items    the items to be registered for
+	 */
+	public static void registerModel(@Nullable ModelProvider provider, Iterable<Item> items) {
+		ArmorRenderingRegistryImpl.registerModel(provider, items);
+	}
+
+	/**
+	 * Registers a provider for custom texture models for an item.
+	 *
+	 * @param provider the provider for the texture
+	 * @param items    the items to be registered for
+	 */
+	public static void registerTexture(@Nullable TextureProvider provider, Item... items) {
+		registerTexture(provider, Arrays.asList(items));
+	}
+
+	/**
+	 * Registers a provider for custom texture models for an item.
+	 *
+	 * @param provider the provider for the texture
+	 * @param items    the items to be registered for
+	 */
+	public static void registerTexture(@Nullable TextureProvider provider, Iterable<Item> items) {
+		ArmorRenderingRegistryImpl.registerTexture(provider, items);
+	}
+
+	/**
+	 * Register simple armor items to use the vanilla armor file name under the mods namespace.
+	 *
+	 * @param identifier The namespace + path to use for the armor texture location.
+	 * @param items the items to be registered
+	 */
+	public static void registerSimpleTexture(Identifier identifier, Item... items) {
+		registerTexture((entity, stack, slot, secondLayer, suffix, defaultTexture) -> {
+			return new Identifier(identifier.getNamespace(), "textures/models/armor/" + identifier.getPath() + "_layer_" + (secondLayer ? 2 : 1) + (suffix == null ? "" : "_" + suffix) + ".png");
+		}, items);
+	}
+
+	/**
+	 * Gets the model of the armor piece.
+	 *
+	 * @param entity       The entity equipping the armor
+	 * @param stack        The item stack of the armor
+	 * @param slot         The slot which the armor is in
+	 * @param defaultModel The default model that vanilla provides
+	 * @return The model of the armor piece.
+	 */
+	@NotNull
+	public static BipedEntityModel<LivingEntity> getArmorModel(LivingEntity entity, ItemStack stack, EquipmentSlot slot, BipedEntityModel<LivingEntity> defaultModel) {
+		return ArmorRenderingRegistryImpl.getArmorModel(entity, stack, slot, defaultModel);
+	}
+
+	/**
+	 * Gets the armor texture {@link net.minecraft.util.Identifier}.
+	 *
+	 * @param entity         The entity equipping the armor
+	 * @param stack          The item stack of the armor
+	 * @param slot           The slot which the armor is in
+	 * @param secondLayer	 True if using the second texture layer
+	 * @param suffix         The texture suffix, used in vanilla by {@link net.minecraft.item.DyeableArmorItem}
+	 * @param defaultTexture The default vanilla texture identifier
+	 * @return the custom armor texture identifier, return null to use the vanilla ones. Defaulted to the item's registry id.
+	 */
+	@NotNull
+	public static Identifier getArmorTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, boolean secondLayer, @Nullable String suffix, Identifier defaultTexture) {
+		return ArmorRenderingRegistryImpl.getArmorTexture(entity, stack, slot, secondLayer, suffix, defaultTexture);
+	}
+
+	@FunctionalInterface
+	@Environment(EnvType.CLIENT)
+	public interface ModelProvider {
+		/**
+		 * Gets the model of the armor piece.
+		 *
+		 * @param entity       The entity equipping the armor
+		 * @param stack        The item stack of the armor
+		 * @param slot         The slot which the armor is in
+		 * @param defaultModel The default vanilla armor model
+		 * @return The model of the armor piece. Should never be null.
+		 */
+		@NotNull
+		BipedEntityModel<LivingEntity> getArmorModel(LivingEntity entity, ItemStack stack, EquipmentSlot slot, BipedEntityModel<LivingEntity> defaultModel);
+	}
+
+	@FunctionalInterface
+	@Environment(EnvType.CLIENT)
+	public interface TextureProvider {
+		/**
+		 * Gets the armor texture {@link net.minecraft.util.Identifier}.
+		 *
+		 * @param entity         The entity equipping the armor
+		 * @param stack          The item stack of the armor
+		 * @param slot           The slot which the armor is in
+		 * @param defaultTexture The default vanilla texture identifier
+		 * @return the custom armor texture identifier. Should never be null.
+		 */
+		@NotNull
+		Identifier getArmorTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, boolean secondLayer, @Nullable String suffix, Identifier defaultTexture);
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorProviderExtensions.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorProviderExtensions.java
new file mode 100644
index 000000000..13166aa39
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorProviderExtensions.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderingRegistry;
+
+public interface ArmorProviderExtensions {
+	@Nullable
+	ArmorRenderingRegistry.ModelProvider fabric_getArmorModelProvider();
+
+	@Nullable
+	ArmorRenderingRegistry.TextureProvider fabric_getArmorTextureProvider();
+
+	void fabric_setArmorModelProvider(@Nullable ArmorRenderingRegistry.ModelProvider provider);
+
+	void fabric_setArmorTextureProvider(@Nullable ArmorRenderingRegistry.TextureProvider provider);
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorRenderingRegistryImpl.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorRenderingRegistryImpl.java
new file mode 100644
index 000000000..4bd3a91e6
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/ArmorRenderingRegistryImpl.java
@@ -0,0 +1,79 @@
+/*
+ * 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.Objects;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.client.render.entity.model.BipedEntityModel;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+
+import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderingRegistry;
+
+public final class ArmorRenderingRegistryImpl {
+	private ArmorRenderingRegistryImpl() {
+	}
+
+	public static void registerModel(ArmorRenderingRegistry.ModelProvider provider, Iterable<Item> items) {
+		Objects.requireNonNull(items);
+
+		for (Item item : items) {
+			Objects.requireNonNull(item);
+
+			((ArmorProviderExtensions) item).fabric_setArmorModelProvider(provider);
+		}
+	}
+
+	public static void registerTexture(ArmorRenderingRegistry.TextureProvider provider, Iterable<Item> items) {
+		Objects.requireNonNull(items);
+
+		for (Item item : items) {
+			Objects.requireNonNull(item);
+
+			((ArmorProviderExtensions) item).fabric_setArmorTextureProvider(provider);
+		}
+	}
+
+	public static BipedEntityModel<LivingEntity> getArmorModel(LivingEntity entity, ItemStack stack, EquipmentSlot slot, BipedEntityModel<LivingEntity> defaultModel) {
+		if (!stack.isEmpty()) {
+			ArmorRenderingRegistry.ModelProvider provider = ((ArmorProviderExtensions) stack.getItem()).fabric_getArmorModelProvider();
+
+			if (provider != null) {
+				return provider.getArmorModel(entity, stack, slot, defaultModel);
+			}
+		}
+
+		return defaultModel;
+	}
+
+	public static Identifier getArmorTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, boolean secondLayer, @Nullable String suffix, Identifier defaultTexture) {
+		if (!stack.isEmpty()) {
+			ArmorRenderingRegistry.TextureProvider provider = ((ArmorProviderExtensions) stack.getItem()).fabric_getArmorTextureProvider();
+
+			if (provider != null) {
+				return provider.getArmorTexture(entity, stack, slot, secondLayer, suffix, defaultTexture);
+			}
+		}
+
+		return defaultTexture;
+	}
+}
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..55d646657
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinArmorFeatureRenderer.java
@@ -0,0 +1,103 @@
+/*
+ * 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 java.util.Map;
+import java.util.Objects;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+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.ArmorItem;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderingRegistry;
+
+@Mixin(ArmorFeatureRenderer.class)
+@Environment(EnvType.CLIENT)
+public abstract class MixinArmorFeatureRenderer extends FeatureRenderer {
+	@Shadow
+	@Final
+	private static Map<String, Identifier> ARMOR_TEXTURE_CACHE;
+
+	public MixinArmorFeatureRenderer(FeatureRendererContext context) {
+		super(context);
+	}
+
+	@Unique
+	private LivingEntity storedEntity;
+	@Unique
+	private EquipmentSlot storedSlot;
+
+	@Inject(method = "render", at = @At("HEAD"))
+	private void storeEntity(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, LivingEntity livingEntity, float f, float g, float h, float j, float k, float l, CallbackInfo ci) {
+		// We store the living entity wearing the armor before we render
+		this.storedEntity = livingEntity;
+	}
+
+	@Inject(method = "renderArmor", at = @At("HEAD"))
+	private void storeSlot(MatrixStack matrices, VertexConsumerProvider vertexConsumers, LivingEntity livingEntity, EquipmentSlot slot, int i, BipedEntityModel bipedEntityModel, CallbackInfo ci) {
+		// We store the current armor slot that is rendering before we render each armor piece
+		this.storedSlot = slot;
+	}
+
+	@Inject(method = "render", at = @At("RETURN"))
+	private void removeStored(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, LivingEntity livingEntity, float f, float g, float h, float j, float k, float l, CallbackInfo ci) {
+		// We remove the stored data after we render
+		this.storedEntity = null;
+		this.storedSlot = null;
+	}
+
+	@Inject(method = "getArmor", at = @At("RETURN"), cancellable = true)
+	private void selectArmorModel(EquipmentSlot slot, CallbackInfoReturnable<BipedEntityModel<LivingEntity>> cir) {
+		ItemStack stack = storedEntity.getEquippedStack(slot);
+
+		BipedEntityModel<LivingEntity> defaultModel = cir.getReturnValue();
+		BipedEntityModel<LivingEntity> model = ArmorRenderingRegistry.getArmorModel(storedEntity, stack, slot, defaultModel);
+
+		if (model != defaultModel) {
+			cir.setReturnValue(model);
+		}
+	}
+
+	@Inject(method = "getArmorTexture", at = @At(value = "INVOKE", target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD)
+	private void getArmorTexture(ArmorItem armorItem, boolean secondLayer, /* @Nullable */ String suffix, CallbackInfoReturnable<Identifier> cir, String vanillaIdentifier) {
+		String texture = ArmorRenderingRegistry.getArmorTexture(storedEntity, storedEntity.getEquippedStack(storedSlot), storedSlot, secondLayer, suffix, new Identifier(vanillaIdentifier)).toString();
+
+		if (!Objects.equals(texture, vanillaIdentifier)) {
+			cir.setReturnValue(ARMOR_TEXTURE_CACHE.computeIfAbsent(texture, Identifier::new));
+		}
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinItem.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinItem.java
new file mode 100644
index 000000000..7d3fc300b
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinItem.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.Unique;
+
+import net.minecraft.item.Item;
+
+import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderingRegistry;
+import net.fabricmc.fabric.impl.client.rendering.ArmorProviderExtensions;
+
+@Mixin(Item.class)
+public class MixinItem implements ArmorProviderExtensions {
+	@Unique
+	private ArmorRenderingRegistry.ModelProvider armorModelProvider;
+	@Unique
+	private ArmorRenderingRegistry.TextureProvider armorTextureProvider;
+
+	@Override
+	public ArmorRenderingRegistry.ModelProvider fabric_getArmorModelProvider() {
+		return armorModelProvider;
+	}
+
+	@Override
+	public ArmorRenderingRegistry.TextureProvider fabric_getArmorTextureProvider() {
+		return armorTextureProvider;
+	}
+
+	@Override
+	public void fabric_setArmorModelProvider(ArmorRenderingRegistry.ModelProvider provider) {
+		armorModelProvider = provider;
+	}
+
+	@Override
+	public void fabric_setArmorTextureProvider(ArmorRenderingRegistry.TextureProvider provider) {
+		armorTextureProvider = provider;
+	}
+}
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 aad088181..3f55d6a8a 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,9 +3,11 @@
   "package": "net.fabricmc.fabric.mixin.client.rendering",
   "compatibilityLevel": "JAVA_8",
   "client": [
+    "MixinArmorFeatureRenderer",
     "MixinBlockColorMap",
     "MixinBuiltinModelItemRenderer",
     "MixinInGameHud",
+    "MixinItem",
     "MixinItemColorMap",
     "MixinWorldRenderer"
   ],
diff --git a/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/CustomArmorTests.java b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/CustomArmorTests.java
new file mode 100644
index 000000000..b2311eac9
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/CustomArmorTests.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.item.ArmorItem;
+import net.minecraft.item.ArmorMaterials;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemGroup;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+
+import net.fabricmc.api.ModInitializer;
+
+public class CustomArmorTests implements ModInitializer {
+	public static Item customModeledArmor;
+	public static Item customTexturedArmor;
+	public static Item simpleTexturedArmor;
+
+	@Override
+	public void onInitialize() {
+		Registry.register(Registry.ITEM, new Identifier("fabric-rendering-v1-testmod:custom_modeled_armor"),
+				customModeledArmor = new ArmorItem(ArmorMaterials.DIAMOND, EquipmentSlot.CHEST, new Item.Settings().group(ItemGroup.COMBAT)));
+
+		Registry.register(Registry.ITEM, new Identifier("fabric-rendering-v1-testmod:custom_textured_armor"),
+				customTexturedArmor = new ArmorItem(ArmorMaterials.DIAMOND, EquipmentSlot.CHEST, new Item.Settings().group(ItemGroup.COMBAT)));
+
+		Registry.register(Registry.ITEM, new Identifier("fabric-rendering-v1-testmod:simple_textured_armor"),
+				simpleTexturedArmor = new ArmorItem(ArmorMaterials.DIAMOND, EquipmentSlot.CHEST, new Item.Settings().group(ItemGroup.COMBAT)));
+	}
+}
diff --git a/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/CustomArmorTestsClient.java b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/CustomArmorTestsClient.java
new file mode 100644
index 000000000..db22faad6
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/CustomArmorTestsClient.java
@@ -0,0 +1,68 @@
+/*
+ * 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 java.util.Collections;
+
+import net.minecraft.client.model.ModelPart;
+import net.minecraft.client.render.entity.model.BipedEntityModel;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.util.Identifier;
+
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderingRegistry;
+import net.fabricmc.fabric.test.rendering.CustomArmorTests;
+
+@Environment(EnvType.CLIENT)
+public class CustomArmorTestsClient implements ClientModInitializer {
+	@Override
+	public void onInitializeClient() {
+		CustomArmorModel model = new CustomArmorModel(1.0F);
+		ArmorRenderingRegistry.registerModel((entity, stack, slot, defaultModel) -> model, CustomArmorTests.customModeledArmor);
+		ArmorRenderingRegistry.registerTexture((entity, stack, slot, secondLayer, suffix, defaultTexture) ->
+				new Identifier("fabric-rendering-v1-testmod", "textures/cube.png"), CustomArmorTests.customModeledArmor);
+
+		ArmorRenderingRegistry.registerTexture((entity, stack, slot, secondLayer, suffix, defaultTexture) ->
+				new Identifier("fabric-rendering-v1-testmod", "textures/custom_texture.png"), CustomArmorTests.customTexturedArmor);
+
+		ArmorRenderingRegistry.registerSimpleTexture(new Identifier("fabric-rendering-v1-testmod", "simple_textured_armor"), CustomArmorTests.simpleTexturedArmor);
+	}
+
+	private static class CustomArmorModel extends BipedEntityModel<LivingEntity> {
+		private final ModelPart part;
+
+		CustomArmorModel(float scale) {
+			super(scale, 0, 1, 1);
+			part = new ModelPart(this, 0, 0);
+			part.addCuboid(-5F, 0F, 2F, 10, 10, 10);
+			part.setPivot(0F, 0F, 0F);
+			part.mirror = true;
+		}
+
+		@Override
+		protected Iterable<ModelPart> getBodyParts() {
+			return Collections.singleton(part);
+		}
+
+		@Override
+		protected Iterable<ModelPart> getHeadParts() {
+			return Collections::emptyIterator;
+		}
+	}
+}
diff --git a/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/cube.png b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/cube.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ae593cc53ce949e1a8649fde3e7646a6f28c038
GIT binary patch
literal 471
zcmV;|0Vw{7P)<h;3K|Lk000e1NJLTq00031000391^@s69~H!j0004nX+uL$Nkc;*
zaB^>EX>4Tx04R}tkv&MmKpe$iQ%gmv4t5afkf92K1yK=4twIqhlv<%x2a`*`ph-iL
z;^HW{799LotU9<j>+0Yt2!bCVPL58BE>hzEl0u6Z503ls?%w0>9UwF+Of|d40ade%
zbRsThbE{(T6+uMMhX6(;X6kdPR1%)!>mEM7-o<#9_qjhuuaY+z;1h{wnQmCb8^qI_
zmd<&fILu0tLVQj<X3zzRAGt2O{KmQHu)s6JMkYN^93~cv9V~Y+D;X;B6me8hHOd#V
zE-Re3IIEQ!Yu%H-FqGF;mbp$df&><^gcL-`sG*DsEW~KlNHLM7{kVsJ$nmGhC6lWR
zMvetkp+a)};D7MDTeC1Z;U<ORK;Xr;KZb$eF3_yo_V=-EH%|cnGjOG~{nZ9A^GSNW
ztwoQ3{%zpmx~<83z~v4w_@qmQ<Vb#+LZJY>pV2qvfPq_}XU*-cwU5&WAVXaxZ-9eC
zV5CUd>mKj!?(FT~Gp+u909T}PoD`t}ga7~l4M{{nR0x@4U|?YQ4*&rK0RVln$lm|}
N002ovPDHLkV1gW6&L98)

literal 0
HcmV?d00001

diff --git a/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/custom_texture.png b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/custom_texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..d4bcbb9d681d3829bdb1ff36f56a25deedad4362
GIT binary patch
literal 670
zcmV;P0%84$P)<h;3K|Lk000e1NJLTq002M$001Be1^@s6qMd$(0004nX+uL$Nkc;*
zaB^>EX>4Tx04R}tkv&MmKpe$iQ%gmv4t5afkf92K1yK=4twIqhlv<%x2a`*`ph-iL
z;^HW{799LotU9<j>+0Yt2!bCVPL58BE>hzEl0u6Z503ls?%w0>9UwF+Of|d40ade%
zbRsThbE{(T6+uMMhX6(;X6kdPR1%)!>mEM7-o<#9_qjhuuaY+z;1h{wnQmCb8^qI_
zmd<&fILu0tLVQj<X3zzRAGt2O{KmQHu)s6JMkYN^93~cv9V~Y+D;X;B6me8hHOd#V
zE-Re3IIEQ!Yu%H-FqGF;mbp$df&><^gcL-`sG*DsEW~KlNHLM7{kVsJ$nmGhC6lWR
zMvetkp+a)};D7MDTeC1Z;U<ORK;Xr;KZb$eF3_yo_V=-EH%|cnGjOG~{nZ9A^GSNW
ztwoQ3{%zpmx~<83z~v4w_@qmQ<Vb#+LZJY>pV2qvfPq_}XU*-cwU5&WAVXaxZ-9eC
zV5CUd>mKj!?(FT~Gp+u909T}PoD`t}ga7~l24YJ`L;(K)0000pCw%h&000SaNLh0L
z04^f{04^f|c%?sf00007bV*G`2jmAF4hJU*JISR0000?uMObu0Z*6U5Zgc=ca%Ew3
zWn>_CX>@2HM@dakSAh-}0001WNkl<ZXx{DAOAde_3`NmCc2f7hCRs|InizFJFx1{N
zBWd_NC}scv0000004mYw1nwT^@!tDm{pN1B^)j=#Y)~@;XiHs3e7|H{_;F<qAX4Pj
zDnI}A{s02_Qvj+SK;#HO6V4qk3u>f3*B_wDEs&%Q4j^F>7TA@Z<NyEw07*qoM6N<$
Ef}DjGBme*a

literal 0
HcmV?d00001

diff --git a/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/models/armor/simple_textured_armor_layer_1.png b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/textures/models/armor/simple_textured_armor_layer_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..e29f81a2a3de238ab2d8d04b9674533e82b05912
GIT binary patch
literal 141
zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!VDw>HYaZfQjEnx?oJHr&dIz4a#+$GeH|GX
zHuiJ>Nn{1`nFD-6TzzW(7Zgq41adhGJR*x382Ao@Fyrz36)8YLVNVyw5R22v60D08
gM0%LkG&C|YuqZGvJ{NPc1WGe_y85}Sb4q9e0F2Qgp8x;=

literal 0
HcmV?d00001

diff --git a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json
new file mode 100644
index 000000000..342edb10e
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json
@@ -0,0 +1,19 @@
+{
+  "schemaVersion": 1,
+  "id": "fabric-rendering-v1-testmod",
+  "name": "Fabric Rendering (v1) Test Mod",
+  "version": "1.0.0",
+  "environment": "*",
+  "license": "Apache-2.0",
+  "depends": {
+    "fabric-rendering-v1": "*"
+  },
+  "entrypoints": {
+    "main": [
+      "net.fabricmc.fabric.test.rendering.CustomArmorTests"
+    ],
+    "client": [
+      "net.fabricmc.fabric.test.rendering.client.CustomArmorTestsClient"
+    ]
+  }
+}