feat: render GeckoLib cosmetics on player bodies

This commit is contained in:
2026-07-14 15:27:01 +02:00
parent c7bd7b8444
commit 53fbc1705c
3 changed files with 112 additions and 0 deletions
@@ -0,0 +1,29 @@
package de.fullrisk.citrusDev.client.cosmetic;
import com.geckolib.model.GeoModel;
import com.geckolib.renderer.base.GeoRenderState;
import net.minecraft.resources.Identifier;
public final class PlayerCosmeticModel extends GeoModel<PlayerCosmetic> {
private static final Identifier MODEL = Identifier.fromNamespaceAndPath(
"citrus-dev", "geckolib/models/player/backpack.geo.json");
private static final Identifier TEXTURE = Identifier.fromNamespaceAndPath(
"citrus-dev", "textures/cosmetic/backpack.png");
private static final Identifier ANIMATION = Identifier.fromNamespaceAndPath(
"citrus-dev", "geckolib/animations/backpack.animation.json");
@Override
public Identifier getModelResource(GeoRenderState renderState) {
return MODEL;
}
@Override
public Identifier getTextureResource(GeoRenderState renderState) {
return TEXTURE;
}
@Override
public Identifier getAnimationResource(PlayerCosmetic animatable) {
return ANIMATION;
}
}
@@ -0,0 +1,57 @@
package de.fullrisk.citrusDev.client.cosmetic;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.player.PlayerModel;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.client.renderer.entity.state.AvatarRenderState;
import net.minecraft.client.renderer.state.level.CameraRenderState;
public final class PlayerCosmeticRenderLayer extends RenderLayer<AvatarRenderState, PlayerModel> {
// Blockbench uses Minecraft model units around the vanilla body origin; no corrective transform is needed.
private static final float MODEL_SCALE = 1.0F;
private final PlayerCosmeticRenderer renderer = new PlayerCosmeticRenderer();
public PlayerCosmeticRenderLayer(RenderLayerParent<AvatarRenderState, PlayerModel> parent) {
super(parent);
}
@Override
public void submit(
PoseStack poseStack,
SubmitNodeCollector nodeCollector,
int packedLight,
AvatarRenderState state,
float limbAngle,
float limbDistance
) {
CosmeticRenderData renderData = state.getData(CosmeticRenderStateKeys.COSMETIC);
CameraRenderState cameraRenderState = state.getData(CosmeticRenderStateKeys.CAMERA);
if (renderData == null || cameraRenderState == null || state.isInvisible || state.isSpectator
|| !renderData.cosmetic().visible()) {
return;
}
PlayerCosmetic animatable = PlayerCosmeticManager.getOrCreateAnimatable(renderData.playerId());
poseStack.pushPose();
try {
getParentModel().body.translateAndRotate(poseStack);
poseStack.scale(MODEL_SCALE, MODEL_SCALE, MODEL_SCALE);
renderer.performRenderPass(
animatable,
renderData,
poseStack,
nodeCollector,
cameraRenderState,
packedLight,
renderData.partialTick()
);
} finally {
poseStack.popPose();
}
}
}
@@ -0,0 +1,26 @@
package de.fullrisk.citrusDev.client.cosmetic;
import com.geckolib.renderer.GeoObjectRenderer;
import com.geckolib.renderer.base.GeoRenderState;
import com.geckolib.renderer.base.RenderPassInfo;
import java.util.UUID;
public final class PlayerCosmeticRenderer extends GeoObjectRenderer<
PlayerCosmetic, CosmeticRenderData, GeoRenderState> {
public PlayerCosmeticRenderer() {
super(new PlayerCosmeticModel());
}
@Override
public long getInstanceId(PlayerCosmetic animatable, CosmeticRenderData data) {
UUID uuid = data.playerId();
return uuid.getMostSignificantBits() ^ uuid.getLeastSignificantBits();
}
@Override
public void adjustRenderPose(RenderPassInfo<GeoRenderState> renderPassInfo) {
// The layer already supplies a body-local pose; omit GeoObjectRenderer's block-centering offset.
}
}