Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,19 @@

package net.fabricmc.fabric.api.client.rendering.v1;

import com.mojang.datafixers.util.Pair;
import org.jetbrains.annotations.Nullable;

import net.minecraft.client.model.Model;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.command.ModelCommandRenderer;
import net.minecraft.client.render.command.OrderedRenderCommandQueue;
import net.minecraft.client.render.command.RenderCommandQueue;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.client.render.entity.state.BipedEntityRenderState;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
Expand All @@ -32,10 +41,22 @@
* Armor renderers render worn armor items with custom code.
* They may be used to render armor with special models or effects.
*
* <p>The renderers are registered with {@link net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer#register(ArmorRenderer, ItemConvertible...)}.
* <p>The renderers are registered with {@link net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer#register(Factory, ItemConvertible...)}
* or {@link net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer#register(ArmorRenderer, ItemConvertible...)}.
*/
@FunctionalInterface
public interface ArmorRenderer {
/**
* Registers the armor renderer for the specified items.
* @param factory the renderer factory
* @param items the items
* @throws IllegalArgumentException if an item already has a registered armor renderer
* @throws NullPointerException if either an item or the factory is null
*/
static void register(ArmorRenderer.Factory factory, ItemConvertible... items) {
ArmorRendererRegistryImpl.register(factory, items);
}

/**
* Registers the armor renderer for the specified items.
* @param renderer the renderer
Expand All @@ -47,6 +68,54 @@ static void register(ArmorRenderer renderer, ItemConvertible... items) {
ArmorRendererRegistryImpl.register(renderer, items);
}

/**
* Helper method for rendering a {@link TransformCopyingModel}, which will copy transforms from a source model to
* a delegate model when it is rendered.
* @param sourceModel the model whose transforms will be copied
* @param sourceModelState the model state of the source model
* @param delegateModel the model that will be rendered with transforms copied from the source model
* @param delegateModelState the model state of the delegate model
* @param setDelegateAngles {@code true} if the {@link Model#setAngles(Object)} method should be called for the
* delegate model after it is called for the source model
* @param queue the {@link RenderCommandQueue}
* @param matrices the matrix stack
* @param renderLayer the render layer
* @param light packed lightmap coordinates
* @param overlay packed overlay texture coordinates
* @param tintedColor the color to tint the model with
* @param sprite the sprite to render the model with, or {@code null} to use the render layer instead
* @param outlineColor the outline color of the model
* @param crumblingOverlay the crumbling overlay, or {@code null} for no crumbling overlay
* @param <S> state type of the source model
* @param <D> state type of the delegate model
*/
static <S, D> void submitTransformCopyingModel(Model<? super S> sourceModel, S sourceModelState, Model<? super D> delegateModel, D delegateModelState, boolean setDelegateAngles, RenderCommandQueue queue, MatrixStack matrices, RenderLayer renderLayer, int light, int overlay, int tintedColor, @Nullable Sprite sprite, int outlineColor, @Nullable ModelCommandRenderer.CrumblingOverlayCommand crumblingOverlay) {
queue.submitModel(TransformCopyingModel.create(sourceModel, delegateModel, setDelegateAngles), Pair.of(sourceModelState, delegateModelState), matrices, renderLayer, light, overlay, tintedColor, sprite, outlineColor, crumblingOverlay);
}

/**
* Helper method for rendering a {@link TransformCopyingModel}, which will copy transforms from its source model to
* its delegate model when it is rendered.
* @param sourceModel the model whose transforms will be copied
* @param sourceModelState the model state of the source model
* @param delegateModel the model that will be rendered with transforms copied from the source model
* @param delegateModelState the model state of the delegate model
* @param setDelegateAngles {@code true} if the {@link Model#setAngles(Object)} method should be called for the
* delegate model after it is called for the source model
* @param queue the {@link RenderCommandQueue}
* @param matrices the matrix stack
* @param renderLayer the render layer
* @param light packed lightmap coordinates
* @param overlay packed overlay texture coordinates
* @param outlineColor the outline color of the model
* @param crumblingOverlay the crumbling overlay, or {@code null} for no crumbling overlay
* @param <S> state type of the source model
* @param <D> state type of the delegate model
*/
static <S, D> void submitTransformCopyingModel(Model<? super S> sourceModel, S sourceModelState, Model<? super D> delegateModel, D delegateModelState, boolean setDelegateAngles, RenderCommandQueue queue, MatrixStack matrices, RenderLayer renderLayer, int light, int overlay, int outlineColor, @Nullable ModelCommandRenderer.CrumblingOverlayCommand crumblingOverlay) {
queue.submitModel(TransformCopyingModel.create(sourceModel, delegateModel, setDelegateAngles), Pair.of(sourceModelState, delegateModelState), matrices, renderLayer, light, overlay, outlineColor, crumblingOverlay);
}

/**
* Renders an armor part.
*
Expand Down Expand Up @@ -79,4 +148,12 @@ static void register(ArmorRenderer renderer, ItemConvertible... items) {
default boolean shouldRenderDefaultHeadItem(LivingEntity entity, ItemStack stack) {
return true;
}

/**
* A factory to create an {@link ArmorRenderer} instance.
*/
@FunctionalInterface
interface Factory {
ArmorRenderer createArmorRenderer(EntityRendererFactory.Context context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import net.minecraft.client.model.TexturedModelData;
import net.minecraft.client.render.entity.model.EntityModelLayer;
import net.minecraft.client.render.entity.model.EquipmentModelData;

import net.fabricmc.fabric.impl.client.rendering.EntityModelLayerImpl;
import net.fabricmc.fabric.mixin.client.rendering.EntityModelLayersAccessor;
Expand All @@ -45,6 +46,22 @@ public static void registerModelLayer(EntityModelLayer modelLayer, TexturedModel
EntityModelLayersAccessor.getLayers().add(modelLayer);
}

/**
* Registers entity equipment model layers and registers a provider for a {@link EquipmentModelData} of type {@link TexturedModelData}.
* @param equipmentModelData the equipment model data of type {@link EntityModelLayer}
* @param provider the provider for the textured equipment model data
*/
public static void registerEquipmentModelLayers(EquipmentModelData<EntityModelLayer> equipmentModelData, TexturedEquipmentModelDataProvider provider) {
Objects.requireNonNull(equipmentModelData, "EquipmentModelData cannot be null");
Objects.requireNonNull(provider, "TexturedEquipmentModelDataProvider cannot be null");

if (EntityModelLayerImpl.EQUIPMENT_PROVIDERS.putIfAbsent(equipmentModelData, provider) != null) {
throw new IllegalArgumentException(String.format("Cannot replace registration for entity equipment model layer \"%s\"", equipmentModelData));
}

equipmentModelData.map(EntityModelLayersAccessor.getLayers()::add);
}

private EntityModelLayerRegistry() {
}

Expand All @@ -57,4 +74,14 @@ public interface TexturedModelDataProvider {
*/
TexturedModelData createModelData();
}

@FunctionalInterface
public interface TexturedEquipmentModelDataProvider {
/**
* Creates the textured model data for use in a {@link EquipmentModelData} of type {@link TexturedModelData}.
*
* @return the textured model data for the entity model layer.
*/
EquipmentModelData<TexturedModelData> createEquipmentModelData();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

import net.minecraft.client.model.Model;
import net.minecraft.client.model.ModelPart;

/**
* General purpose Fabric extensions to the {@link Model} class.
*
* <p>Note: This interface is automatically implemented on all {@link Model} instances via Mixin and interface injection.
*/
@ApiStatus.NonExtendable
public interface FabricModel<S> {
/**
* Returns a child model part of the given name, or {@code null} if one is not found.
* @param name the name of the child model part
* @return the child model part that corresponds to the name parameter, or {@code null} if it is not found.
*/
@Nullable
default ModelPart getChildPart(String name) {
throw new UnsupportedOperationException("Implemented via mixin");
}

/**
* Copies transforms of child model parts of the model to child model parts of this model whose names match.
* @param model the model to copy transforms from
*/
default void copyTransforms(Model<?> model) {
throw new UnsupportedOperationException("Implemented via mixin");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 com.mojang.datafixers.util.Pair;

import net.minecraft.client.model.Model;

/**
* A model that copies transforms from a source model to a delegate model.
*
* <p>Useful in cases where an overlay model may not be a subclass of its base model.
* @param <S> type of the source model state
* @param <D> type of the delegate model state
*/
public final class TransformCopyingModel<S, D> extends Model<Pair<S, D>> {
private final Model<? super S> source;
private final Model<? super D> delegate;
private final boolean setDelegateAngles;

/**
* @param source the model whose transforms will be copied
* @param delegate the model that will be rendered with transforms copied from the source model
* @param setDelegateAngles {@code true} if the {@link Model#setAngles(Object)} method should be called for the
* delegate model after it is called for the source model
*/
public static <S, D> TransformCopyingModel<S, D> create(Model<? super S> source, Model<? super D> delegate, boolean setDelegateAngles) {
return new TransformCopyingModel<>(source, delegate, setDelegateAngles);
}

private TransformCopyingModel(Model<? super S> source, Model<? super D> delegate, boolean setDelegateAngles) {
super(delegate.getRootPart(), delegate::getLayer);
this.source = source;
this.delegate = delegate;
this.setDelegateAngles = setDelegateAngles;
}

@Override
public void setAngles(Pair<S, D> state) {
resetTransforms();
source.setAngles(state.getFirst());
delegate.copyTransforms(source);

if (setDelegateAngles) {
delegate.setAngles(state.getSecond());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@

import org.jetbrains.annotations.Nullable;

import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemConvertible;
import net.minecraft.registry.Registries;

import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer;

public class ArmorRendererRegistryImpl {
private static final HashMap<Item, ArmorRenderer.Factory> FACTORIES = new HashMap<>();
private static final HashMap<Item, ArmorRenderer> RENDERERS = new HashMap<>();

public static void register(ArmorRenderer renderer, ItemConvertible... items) {
Objects.requireNonNull(renderer, "renderer is null");
public static void register(ArmorRenderer.Factory factory, ItemConvertible... items) {
Objects.requireNonNull(factory, "renderer factory is null");

if (items.length == 0) {
throw new IllegalArgumentException("Armor renderer registered for no item");
Expand All @@ -40,14 +42,24 @@ public static void register(ArmorRenderer renderer, ItemConvertible... items) {
for (ItemConvertible item : items) {
Objects.requireNonNull(item.asItem(), "armor item is null");

if (RENDERERS.putIfAbsent(item.asItem(), renderer) != null) {
if (FACTORIES.putIfAbsent(item.asItem(), factory) != null) {
throw new IllegalArgumentException("Custom armor renderer already exists for " + Registries.ITEM.getId(item.asItem()));
}
}
}

public static void register(ArmorRenderer renderer, ItemConvertible... items) {
Objects.requireNonNull(renderer, "renderer is null");
register(context -> renderer, items);
}

@Nullable
public static ArmorRenderer get(Item item) {
return RENDERERS.get(item);
}

public static void createArmorRenderers(EntityRendererFactory.Context context) {
RENDERERS.clear();
FACTORIES.forEach((item, factory) -> RENDERERS.put(item, factory.createArmorRenderer(context)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import java.util.Map;

import net.minecraft.client.render.entity.model.EntityModelLayer;
import net.minecraft.client.render.entity.model.EquipmentModelData;

import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry;

public final class EntityModelLayerImpl {
public static final Map<EntityModelLayer, EntityModelLayerRegistry.TexturedModelDataProvider> PROVIDERS = new HashMap<>();
public static final Map<EquipmentModelData<EntityModelLayer>, EntityModelLayerRegistry.TexturedEquipmentModelDataProvider> EQUIPMENT_PROVIDERS = new HashMap<>();

private EntityModelLayerImpl() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import net.minecraft.client.model.TexturedModelData;
import net.minecraft.client.render.entity.model.EntityModelLayer;
import net.minecraft.client.render.entity.model.EntityModels;
import net.minecraft.client.render.entity.model.EquipmentModelData;

import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry;
import net.fabricmc.fabric.impl.client.rendering.EntityModelLayerImpl;
Expand All @@ -39,5 +40,9 @@ private static void registerExtraModelData(CallbackInfoReturnable<Map<EntityMode
for (Map.Entry<EntityModelLayer, EntityModelLayerRegistry.TexturedModelDataProvider> entry : EntityModelLayerImpl.PROVIDERS.entrySet()) {
builder.put(entry.getKey(), entry.getValue().createModelData());
}

for (Map.Entry<EquipmentModelData<EntityModelLayer>, EntityModelLayerRegistry.TexturedEquipmentModelDataProvider> entry : EntityModelLayerImpl.EQUIPMENT_PROVIDERS.entrySet()) {
entry.getKey().addTo(entry.getValue().createEquipmentModelData(), builder);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 com.llamalad7.mixinextras.sugar.Local;
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.entity.EntityRenderManager;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.resource.ResourceManager;

import net.fabricmc.fabric.impl.client.rendering.ArmorRendererRegistryImpl;

@Mixin(EntityRenderManager.class)
class EntityRenderManagerMixin {
@Inject(method = "reload", at = @At("TAIL"))
private void createArmorRenderers(ResourceManager manager, CallbackInfo ci, @Local EntityRendererFactory.Context context) {
ArmorRendererRegistryImpl.createArmorRenderers(context);
}
}
Loading
Loading