WIP Phase 2.5 : fork render pipeline (PatchedEntityRenderer family)
Fork EF client renderer patched/entity/ → rig/render/, imports rewrités. Nouveaux fichiers : - render/PatchedEntityRenderer.java (~96 LOC, était stub 16 LOC) — base abstraite, mulPoseStack + setArmaturePose + nameplate via mixin invoker - render/PatchedLivingEntityRenderer.java (~230 LOC, EF 294 → stripped) — hot path render, strippé du JSON customLayers loading + EntityDecorations (pas impl. côté LivingEntityPatch TiedUp) - render/PHumanoidRenderer.java (~95 LOC, EF 53 → adapté) — intermediate biped, baby head scale, enregistre PatchedArmorLayer + PatchedItemInHandLayer - render/TiedUpPlayerRenderer.java (~95 LOC, EF PPlayerRenderer 60 → adapté) — dispatch slim/default (Meshes.ALEX vs BIPED), propage modelParts visible flags, skip cape/bee/arrow layers (V3-REW-08/09) - render/PatchedLayer.java (~55 LOC) — base no-op layer - render/PatchedArmorLayer.java (~45 LOC) — stub no-op (V3-REW-04) - render/PatchedItemInHandLayer.java (~45 LOC) — stub no-op (Phase 3) - mixin/client/MixinEntityRenderer.java (~35 LOC) — invoker shouldShowName + renderNameTag (EF verbatim) - mixin/client/MixinLivingEntityRenderer.java (~30 LOC) — invoker isBodyVisible, getRenderType, getBob Modifs : - resources/tiedup-rig.mixins.json : enregistre les 2 nouveaux mixins client Dépendances stubbées : - PatchedArmorLayer / PatchedItemInHandLayer no-op — "mangent" le layer vanilla équivalent pour éviter double-rendu, mais ne dessinent rien. À implémenter Phase 3 (armor : V3-REW-04 ; item-in-hand : tool joints GLB). - EntityDecorations (color/light/overlay modifiers) strippées du render path — le patch TiedUp n'expose pas encore cette API. Hurt/death overlay natifs conservés. À rework si feature premium glow/tint NPC. TODOs Phase 2.6 / Phase 3 : - animated_layers/*.json datapack-driven loading (strippé du constructor) si besoin futur - PlayerItemInHandLayer (version player-spécifique, distincte de ItemInHandLayer générique) pas encore mangée côté player → layer vanilla continue de draw - ElytraLayer / CustomHeadLayer pas mangés (cohérent "on mange juste les layers qui entrent en conflit avec le mesh skinné") Surprise pendant le fork : - ItemInHandLayer vanilla MC 1.20.1 exige M extends EntityModel<E> & ArmedModel, pas HeadedModel comme PatchedItemInHandLayer EF 1.20.1 pouvait le suggérer via ses anciennes versions. Alignement forcé sur ArmedModel. - EF utilise 2 mixins @Invoker (MixinEntityRenderer / MixinLivingEntityRenderer) pour accéder aux méthodes protected de vanilla — forkés verbatim dans rig/mixin/client/. Tests : - ./gradlew compileJava : BUILD SUCCESSFUL - ./gradlew test --tests "com.tiedup.remake.rig.*" : 15/15 GREEN (TiedUpArmaturesTest 4 + GlbJointAliasTableTest 4 + GltfToSkinnedMeshTest 6 = 14 rig + 1 utilitaire)
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mixin.client;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
/**
|
||||
* Expose les méthodes protected {@code shouldShowName(Entity)} et
|
||||
* {@code renderNameTag(...)} de {@link EntityRenderer} pour que
|
||||
* {@link com.tiedup.remake.rig.render.PatchedEntityRenderer#render}
|
||||
* puisse rendre le nameplate sans les hard-coder.
|
||||
*
|
||||
* <p>Pure @Invoker mixin — pas de logique injectée, équivalent EF de
|
||||
* {@code yesman.epicfight.mixin.client.MixinEntityRenderer}.</p>
|
||||
*/
|
||||
@Mixin(value = EntityRenderer.class)
|
||||
public interface MixinEntityRenderer {
|
||||
|
||||
@Invoker("shouldShowName")
|
||||
boolean invokeShouldShowName(Entity entity);
|
||||
|
||||
@Invoker("renderNameTag")
|
||||
void invokeRenderNameTag(Entity entity, Component name, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mixin.client;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
/**
|
||||
* Expose les méthodes protected {@code isBodyVisible}, {@code getRenderType}
|
||||
* et {@code getBob} de {@link LivingEntityRenderer} pour que
|
||||
* {@link com.tiedup.remake.rig.render.PatchedLivingEntityRenderer#render}
|
||||
* puisse les appeler sans dépendre d'access transformers lourds.
|
||||
*
|
||||
* <p>Pure @Invoker mixin — équivalent EF de
|
||||
* {@code yesman.epicfight.mixin.client.MixinLivingEntityRenderer}.</p>
|
||||
*/
|
||||
@Mixin(value = LivingEntityRenderer.class)
|
||||
public interface MixinLivingEntityRenderer {
|
||||
|
||||
@Invoker("isBodyVisible")
|
||||
boolean invokeIsBodyVisible(LivingEntity entity);
|
||||
|
||||
@Invoker("getRenderType")
|
||||
RenderType invokeGetRenderType(LivingEntity entity, boolean isVisible, boolean isVisibleToPlayer, boolean isGlowing);
|
||||
|
||||
@Invoker("getBob")
|
||||
float invokeGetBob(LivingEntity entity, float partialTicks);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.render;
|
||||
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
|
||||
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.mesh.HumanoidMesh;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
/**
|
||||
* Niveau intermédiaire humanoïde — s'utilise pour tout ce qui rentre dans le
|
||||
* moule biped (player, kidnapper, damsel, maid, master). Ajoute :
|
||||
*
|
||||
* <ul>
|
||||
* <li>Un mesh par défaut injecté au constructor (évite la duplication de
|
||||
* {@link #getDefaultMesh()} dans chaque sous-classe)</li>
|
||||
* <li>Le scale baby Head 1.25x via {@link #setJointTransforms} — pattern EF
|
||||
* strict, le modèle vanilla scale le head au rendu, donc on doit matcher
|
||||
* côté pose pour que les items (gags etc.) restent attachés</li>
|
||||
* <li>Les stubs {@link PatchedArmorLayer} et {@link PatchedItemInHandLayer}
|
||||
* pour "manger" les layers vanilla équivalents (sinon rendu double)</li>
|
||||
* <li>{@link #getDefaultLayerHeightCorrection()} = 0.75F (hauteur standard
|
||||
* humanoïde pour les particles overlays)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <E> entité humanoïde
|
||||
* @param <T> patch RIG associé
|
||||
* @param <M> HumanoidModel vanilla
|
||||
* @param <R> LivingEntityRenderer vanilla qui gère {@code M}
|
||||
* @param <AM> mesh humanoïde (typiquement {@link HumanoidMesh} direct ou
|
||||
* dérivé)
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class PHumanoidRenderer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends HumanoidModel<E>, R extends LivingEntityRenderer<E, M>, AM extends HumanoidMesh> extends PatchedLivingEntityRenderer<E, T, M, R, AM> {
|
||||
|
||||
private final AssetAccessor<AM> mesh;
|
||||
|
||||
public PHumanoidRenderer(AssetAccessor<AM> mesh, EntityRendererProvider.Context context, EntityType<?> entityType) {
|
||||
super(context, entityType);
|
||||
|
||||
this.mesh = mesh;
|
||||
|
||||
// RIG Phase 2.5 : les 2 layers vanilla pertinents (armor + main items)
|
||||
// sont mangés par des stubs no-op. Voir PatchedArmorLayer / PatchedItemInHandLayer
|
||||
// et V3-REW-04 (armor) / Phase 3 (item-in-hand réel).
|
||||
//
|
||||
// ElytraLayer, CustomHeadLayer volontairement non-mappés Phase 2.5 — le
|
||||
// layer vanilla les dessinera directement (couche vanilla séparée du
|
||||
// mesh RIG). Cohérent avec la stratégie "on mange juste les layers dont
|
||||
// la géométrie entre en conflit avec le mesh skinné".
|
||||
this.addPatchedLayer(HumanoidArmorLayer.class, new PatchedArmorLayer<E, T, M>());
|
||||
this.addPatchedLayer(ItemInHandLayer.class, new PatchedItemInHandLayer<E, T, M>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJointTransforms(T entitypatch, Armature armature, Pose pose, float partialTicks) {
|
||||
if (entitypatch.getOriginal().isBaby()) {
|
||||
// Compensation du scale 1.25x que le HumanoidModel vanilla applique sur
|
||||
// la tête quand young==true — sans ça les items tête (gags, masks) se
|
||||
// détachent visuellement du mesh.
|
||||
pose.orElseEmpty("Head").frontResult(JointTransform.scale(new Vec3f(1.25F, 1.25F, 1.25F)), OpenMatrix4f::mul);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getDefaultLayerHeightCorrection() {
|
||||
return 0.75F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetAccessor<AM> getDefaultMesh() {
|
||||
return this.mesh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.render;
|
||||
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
/**
|
||||
* Stub RIG Phase 2.5 — remplace le {@link HumanoidArmorLayer} vanilla pour
|
||||
* les entités riggées.
|
||||
*
|
||||
* <p>Actuellement <b>no-op</b> : le render d'armure n'est pas implémenté
|
||||
* tant que V3-REW-04 n'est pas livré (Phase 3). Enregistré dans
|
||||
* {@link PHumanoidRenderer} pour que la table {@code patchedLayers}
|
||||
* "mange" le layer vanilla sans aucun visuel intermédiaire — évite le
|
||||
* double-rendu armor vanilla par-dessus le mesh skinné.</p>
|
||||
*
|
||||
* <p>Voir {@code docs/plans/rig/V3_REWORK_BACKLOG.md} V3-REW-04 et
|
||||
* {@code PHASE0_DEGRADATIONS.md} S-03.</p>
|
||||
*
|
||||
* @param <E> entité vivante rigg-compatible
|
||||
* @param <T> patch RIG associé
|
||||
* @param <M> HumanoidModel vanilla
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class PatchedArmorLayer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends HumanoidModel<E>> extends PatchedLayer<E, T, M, HumanoidArmorLayer<E, M, M>> {
|
||||
// renderLayer hérité = no-op (voir super). Phase 3 : wire armor bake via
|
||||
// HumanoidMesh.getHumanoidArmorModel + WearableItemLayer-like drawing.
|
||||
}
|
||||
@@ -6,10 +6,90 @@
|
||||
|
||||
package com.tiedup.remake.rig.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.client.event.RenderNameTagEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.eventbus.api.Event.Result;
|
||||
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.QuaternionUtils;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.mixin.client.MixinEntityRenderer;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
/**
|
||||
* Stub RIG Phase 0 — renderer EF substituant {@code EntityRenderer} vanilla
|
||||
* pour les entités riggées. Implémentation complète Phase 2 (pipeline :
|
||||
* pose → armature → skinning → VertexConsumer).
|
||||
* Base abstraite du pipeline de rendu RIG patché — remplace l'{@link EntityRenderer}
|
||||
* vanilla pour les entités dont le patch retourne {@code overrideRender() == true}.
|
||||
*
|
||||
* <p>Fournit trois responsabilités :</p>
|
||||
* <ol>
|
||||
* <li>Render nameplate (via {@link RenderNameTagEvent} Forge + mixin invoker)</li>
|
||||
* <li>Application de la model matrix entité (rotation 180° + yaw corps) au {@link PoseStack}</li>
|
||||
* <li>Baking de la pose courante sur l'{@link Armature} (pour que
|
||||
* {@code armature.getPoseMatrices()} soit à jour avant le draw du mesh)</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>Fork verbatim de {@code yesman.epicfight.client.renderer.patched.entity.PatchedEntityRenderer}
|
||||
* (EF 62 LOC). Imports rewrités vers les packages RIG TiedUp.</p>
|
||||
*
|
||||
* @param <E> entité vivante ({@code LivingEntity}+)
|
||||
* @param <T> patch RIG associé
|
||||
* @param <R> renderer vanilla ({@code EntityRenderer<E>})
|
||||
* @param <AM> mesh skinné dérivé
|
||||
*/
|
||||
public abstract class PatchedEntityRenderer<E, T, M, R> {
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public abstract class PatchedEntityRenderer<E extends LivingEntity, T extends LivingEntityPatch<E>, R extends EntityRenderer<E>, AM extends SkinnedMesh> {
|
||||
|
||||
public void render(E entity, T entitypatch, R renderer, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
|
||||
RenderNameTagEvent renderNameplateEvent = new RenderNameTagEvent(entity, entity.getDisplayName(), renderer, poseStack, buffer, packedLight, partialTicks);
|
||||
MinecraftForge.EVENT_BUS.post(renderNameplateEvent);
|
||||
|
||||
MixinEntityRenderer entityRendererAccessor = (MixinEntityRenderer) renderer;
|
||||
|
||||
if ((entityRendererAccessor.invokeShouldShowName(entity) || renderNameplateEvent.getResult() == Result.ALLOW) && renderNameplateEvent.getResult() != Result.DENY) {
|
||||
entityRendererAccessor.invokeRenderNameTag(entity, renderNameplateEvent.getContent(), poseStack, buffer, packedLight);
|
||||
}
|
||||
}
|
||||
|
||||
public void mulPoseStack(PoseStack poseStack, Armature armature, E entity, T entitypatch, float partialTicks) {
|
||||
OpenMatrix4f modelMatrix = entitypatch.getModelMatrix(partialTicks);
|
||||
poseStack.mulPose(QuaternionUtils.YP.rotationDegrees(180.0F));
|
||||
MathUtils.mulStack(poseStack, modelMatrix);
|
||||
|
||||
if (LivingEntityRenderer.isEntityUpsideDown(entity)) {
|
||||
poseStack.translate(0.0D, entity.getBbHeight() + 0.1F, 0.0D);
|
||||
poseStack.mulPose(QuaternionUtils.ZP.rotationDegrees(180.0F));
|
||||
}
|
||||
}
|
||||
|
||||
public void setArmaturePose(T entitypatch, Armature armature, float partialTicks) {
|
||||
Pose pose = entitypatch.getAnimator().getPose(partialTicks);
|
||||
this.setJointTransforms(entitypatch, armature, pose, partialTicks);
|
||||
armature.setPose(pose);
|
||||
}
|
||||
|
||||
public AssetAccessor<AM> getMeshProvider(T entitypatch) {
|
||||
return this.getDefaultMesh();
|
||||
}
|
||||
|
||||
public abstract AssetAccessor<AM> getDefaultMesh();
|
||||
|
||||
/**
|
||||
* Override pour modifier la pose juste avant le bake (ex. scale de la tête
|
||||
* pour les babies). <b>Developers shouldn't implement any interpolations in
|
||||
* this method</b> — utilise {@link LivingEntityPatch#poseTick} à la place.
|
||||
*/
|
||||
public void setJointTransforms(T entitypatch, Armature armature, Pose pose, float partialTicks) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.render;
|
||||
|
||||
import net.minecraft.client.model.ArmedModel;
|
||||
import net.minecraft.client.model.EntityModel;
|
||||
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
/**
|
||||
* Stub RIG Phase 2.5 — remplace le {@link ItemInHandLayer} vanilla pour les
|
||||
* entités riggées (items tenus en main, main gauche/droite).
|
||||
*
|
||||
* <p>Actuellement <b>no-op</b> : l'item-in-hand RIG-attached arrive en
|
||||
* Phase 3 (items bondage GLB + rendu via tool joints {@code Tool_L} /
|
||||
* {@code Tool_R} du biped). Enregistré dans {@link PHumanoidRenderer}
|
||||
* pour empêcher le layer vanilla de dessiner l'item ailleurs pendant
|
||||
* que le mesh est en pose RIG.</p>
|
||||
*
|
||||
* @param <E> entité tenant un item
|
||||
* @param <T> patch RIG associé
|
||||
* @param <M> {@link EntityModel} qui implémente aussi {@link ArmedModel}
|
||||
* (contrainte vanilla {@link ItemInHandLayer})
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class PatchedItemInHandLayer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends EntityModel<E> & ArmedModel> extends PatchedLayer<E, T, M, ItemInHandLayer<E, M>> {
|
||||
// renderLayer hérité = no-op. Phase 3 : draw hand item via pose matrix du
|
||||
// Tool_L/Tool_R joint sur la base du RenderItemBase existant.
|
||||
}
|
||||
58
src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java
Normal file
58
src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.model.EntityModel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.layers.RenderLayer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
/**
|
||||
* Base abstraite pour les layers additionnels (armor, item-in-hand, cape...)
|
||||
* rendus par dessus le mesh principal. Équivalent EF
|
||||
* {@code client.renderer.patched.layer.PatchedLayer}.
|
||||
*
|
||||
* <p><b>RIG Phase 2.5</b> : infrastructure minimale — la méthode
|
||||
* {@link #renderLayer} est no-op par défaut, et les 2 sous-classes concrètes
|
||||
* (armor / item-in-hand) sont stubs. Implémentations réelles en Phase 3
|
||||
* (V3-REW-04 armor, V3-REW-08/09 cape/arrows).</p>
|
||||
*
|
||||
* @param <E> entité vanilla
|
||||
* @param <T> patch RIG de {@code E}
|
||||
* @param <M> EntityModel vanilla
|
||||
* @param <L> RenderLayer vanilla concret (ex. {@code HumanoidArmorLayer})
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public abstract class PatchedLayer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends EntityModel<E>, L extends RenderLayer<E, M>> {
|
||||
|
||||
/**
|
||||
* Méthode appelée par {@link PatchedLivingEntityRenderer#renderLayer} pour
|
||||
* chaque layer patché enregistré. Stub no-op par défaut.
|
||||
*
|
||||
* @param entity entité rendue
|
||||
* @param entitypatch patch RIG associé
|
||||
* @param originalRender layer vanilla original (peut être {@code null} pour les customLayers)
|
||||
* @param poseStack pose stack courant (déjà en espace entité)
|
||||
* @param buffer buffer source
|
||||
* @param packedLight lumière packée
|
||||
* @param poses matrices de pose armature (index = jointId)
|
||||
* @param bob phase de bobbing vanilla
|
||||
* @param yRotHeadDelta delta entre yRot body et yRot head
|
||||
* @param pitch pitch (xRot) view
|
||||
* @param partialTicks partial ticks
|
||||
*/
|
||||
public void renderLayer(E entity, T entitypatch, L originalRender, PoseStack poseStack, MultiBufferSource buffer, int packedLight, OpenMatrix4f[] poses, float bob, float yRotHeadDelta, float pitch, float partialTicks) {
|
||||
// no-op — subclasses override
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.render;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.model.EntityModel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.layers.RenderLayer;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.event.PrepareModelEvent;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.mixin.client.MixinLivingEntityRenderer;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
/**
|
||||
* Orchestrateur principal du rendu RIG pour les entités vivantes — remplace
|
||||
* {@link LivingEntityRenderer} vanilla pour les entités patched.
|
||||
*
|
||||
* <p>Flow de {@link #render} :</p>
|
||||
* <ol>
|
||||
* <li>super.render : nameplate</li>
|
||||
* <li>mulPoseStack : applique modelMatrix (rotation body + scale + upsideDown)</li>
|
||||
* <li>prepareVanillaModel : setup les champs vanilla du {@link EntityModel} (riding/young,
|
||||
* walkAnimation, head yRot) — utile pour que les layers vanilla non patchés voient
|
||||
* le même état que s'ils étaient rendus par vanilla</li>
|
||||
* <li>setArmaturePose : bake la pose courante dans {@code armature.getPoseMatrices()}</li>
|
||||
* <li>prepareModel : mesh.initialize() (détecte hidden parts)</li>
|
||||
* <li>mesh.draw : dispatch vers shader compute ou CPU skinning via {@code drawPosed}</li>
|
||||
* <li>renderLayer : itère les layers vanilla mappés vers des {@link PatchedLayer} (ex.
|
||||
* armor, item-in-hand, cape) et les customLayers enregistrés manuellement</li>
|
||||
* <li>Debug hitboxes si F3+B actif</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>Stripped vs EF source</b> (volontaire, Phase 2.5) :</p>
|
||||
* <ul>
|
||||
* <li>Chargement JSON datapack-driven des {@code animated_layers/{entity_type}.json} —
|
||||
* TiedUp n'a pas de customLayers data-driven pour l'instant</li>
|
||||
* <li>{@code LayerRenderer} interface (registre public des addPatchedLayer/addCustomLayer) —
|
||||
* conservé en API mais pas exposé via interface dédiée</li>
|
||||
* <li>{@code entitypatch.getEntityDecorations()} — decoration overlays (glow custom, elite
|
||||
* tint) pas implémentés côté {@link LivingEntityPatch} TiedUp. Couleur et overlay
|
||||
* utilisent les defaults vanilla.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <E> entité vivante
|
||||
* @param <T> patch RIG associé
|
||||
* @param <M> EntityModel vanilla
|
||||
* @param <R> renderer vanilla ({@code LivingEntityRenderer})
|
||||
* @param <AM> mesh skinné dérivé
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public abstract class PatchedLivingEntityRenderer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends EntityModel<E>, R extends LivingEntityRenderer<E, M>, AM extends SkinnedMesh> extends PatchedEntityRenderer<E, T, R, AM> {
|
||||
|
||||
protected final Map<Class<?>, PatchedLayer<E, T, M, ? extends RenderLayer<E, M>>> patchedLayers = Maps.newHashMap();
|
||||
protected final List<PatchedLayer<E, T, M, ? extends RenderLayer<E, M>>> customLayers = Lists.newArrayList();
|
||||
|
||||
protected PatchedLivingEntityRenderer(EntityRendererProvider.Context context, EntityType<?> entityType) {
|
||||
// RIG Phase 2.5 : chargement JSON datapack des animated_layers strippé
|
||||
// (EF L55-81). Si besoin futur d'customLayers data-driven, réimplémenter
|
||||
// via FileToIdConverter.json("animated_layers/" + type.getPath()).
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(E entity, T entitypatch, R renderer, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
|
||||
super.render(entity, entitypatch, renderer, buffer, poseStack, packedLight, partialTicks);
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
MixinLivingEntityRenderer livingEntityRendererAccessor = (MixinLivingEntityRenderer) renderer;
|
||||
|
||||
boolean isVisible = livingEntityRendererAccessor.invokeIsBodyVisible(entity);
|
||||
boolean isVisibleToPlayer = !isVisible && !entity.isInvisibleTo(mc.player);
|
||||
boolean isGlowing = mc.shouldEntityAppearGlowing(entity);
|
||||
RenderType renderType = livingEntityRendererAccessor.invokeGetRenderType(entity, isVisible, isVisibleToPlayer, isGlowing);
|
||||
Armature armature = entitypatch.getArmature();
|
||||
|
||||
poseStack.pushPose();
|
||||
this.mulPoseStack(poseStack, armature, entity, entitypatch, partialTicks);
|
||||
this.prepareVanillaModel(entity, renderer.getModel(), renderer, partialTicks);
|
||||
this.setArmaturePose(entitypatch, armature, partialTicks);
|
||||
|
||||
if (renderType != null) {
|
||||
AM mesh = this.getMeshProvider(entitypatch).get();
|
||||
this.prepareModel(mesh, entity, entitypatch, renderer);
|
||||
|
||||
PrepareModelEvent prepareModelEvent = new PrepareModelEvent(this, mesh, entitypatch, buffer, poseStack, packedLight, partialTicks);
|
||||
|
||||
if (!MinecraftForge.EVENT_BUS.post(prepareModelEvent)) {
|
||||
// RIG Phase 2.5 : EntityDecorations (color/light/overlay modifiers) pas
|
||||
// implémentées côté LivingEntityPatch TiedUp. On utilise les defaults
|
||||
// vanilla : alpha 0.15 si invisible-mais-visible-pour-player, sinon 1.0.
|
||||
Vector4f color = new Vector4f(1.0F, 1.0F, 1.0F, isVisibleToPlayer ? 0.15F : 1.0F);
|
||||
|
||||
mesh.draw(poseStack, buffer, renderType, packedLight, color.x(), color.y(), color.z(), color.w(), this.getOverlayCoord(entity, entitypatch, partialTicks), armature, armature.getPoseMatrices());
|
||||
}
|
||||
}
|
||||
|
||||
if (!entity.isSpectator()) {
|
||||
this.renderLayer(renderer, entitypatch, entity, armature.getPoseMatrices(), buffer, poseStack, packedLight, partialTicks);
|
||||
}
|
||||
|
||||
if (renderType != null) {
|
||||
if (mc.getEntityRenderDispatcher().shouldRenderHitBoxes() && entitypatch.getClientAnimator() != null) {
|
||||
entitypatch.getClientAnimator().renderDebuggingInfoForAllLayers(poseStack, buffer, partialTicks);
|
||||
}
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
protected void prepareVanillaModel(E entity, M model, LivingEntityRenderer<E, M> renderer, float partialTicks) {
|
||||
boolean shouldSit = entity.isPassenger() && (entity.getVehicle() != null && entity.getVehicle().shouldRiderSit());
|
||||
model.riding = shouldSit;
|
||||
model.young = entity.isBaby();
|
||||
float f = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot);
|
||||
float f1 = Mth.rotLerp(partialTicks, entity.yHeadRotO, entity.yHeadRot);
|
||||
float f2 = f1 - f;
|
||||
|
||||
if (shouldSit && entity.getVehicle() instanceof LivingEntity livingentity) {
|
||||
f = Mth.rotLerp(partialTicks, livingentity.yBodyRotO, livingentity.yBodyRot);
|
||||
f2 = f1 - f;
|
||||
float f3 = Mth.wrapDegrees(f2);
|
||||
|
||||
if (f3 < -85.0F) {
|
||||
f3 = -85.0F;
|
||||
}
|
||||
|
||||
if (f3 >= 85.0F) {
|
||||
f3 = 85.0F;
|
||||
}
|
||||
|
||||
f = f1 - f3;
|
||||
if (f3 * f3 > 2500.0F) {
|
||||
f += f3 * 0.2F;
|
||||
}
|
||||
|
||||
f2 = f1 - f;
|
||||
}
|
||||
|
||||
float f6 = Mth.lerp(partialTicks, entity.xRotO, entity.getXRot());
|
||||
|
||||
if (LivingEntityRenderer.isEntityUpsideDown(entity)) {
|
||||
f6 *= -1.0F;
|
||||
f2 *= -1.0F;
|
||||
}
|
||||
|
||||
float f7 = ((MixinLivingEntityRenderer) renderer).invokeGetBob(entity, partialTicks);
|
||||
float f8 = 0.0F;
|
||||
float f5 = 0.0F;
|
||||
|
||||
if (!shouldSit && entity.isAlive()) {
|
||||
f8 = entity.walkAnimation.speed(partialTicks);
|
||||
f5 = entity.walkAnimation.position() - entity.walkAnimation.speed() * (1.0F - partialTicks);
|
||||
|
||||
if (entity.isBaby()) {
|
||||
f5 *= 3.0F;
|
||||
}
|
||||
|
||||
if (f8 > 1.0F) {
|
||||
f8 = 1.0F;
|
||||
}
|
||||
}
|
||||
|
||||
model.prepareMobModel(entity, f5, f8, partialTicks);
|
||||
model.setupAnim(entity, f5, f8, f7, f2, f6);
|
||||
}
|
||||
|
||||
protected void prepareModel(AM mesh, E entity, T entitypatch, R renderer) {
|
||||
mesh.initialize();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void renderLayer(LivingEntityRenderer<E, M> renderer, T entitypatch, E entity, OpenMatrix4f[] poses, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
|
||||
float f = MathUtils.lerpBetween(entity.yBodyRotO, entity.yBodyRot, partialTicks);
|
||||
float f1 = MathUtils.lerpBetween(entity.yHeadRotO, entity.yHeadRot, partialTicks);
|
||||
float f2 = f1 - f;
|
||||
float f7 = entity.getViewXRot(partialTicks);
|
||||
float bob = ((MixinLivingEntityRenderer) renderer).invokeGetBob(entity, partialTicks);
|
||||
|
||||
for (RenderLayer<E, M> layer : renderer.layers) {
|
||||
Class<?> layerClass = layer.getClass();
|
||||
|
||||
if (layerClass.isAnonymousClass()) {
|
||||
layerClass = layerClass.getSuperclass();
|
||||
}
|
||||
|
||||
if (this.patchedLayers.containsKey(layerClass)) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
PatchedLayer patchedLayer = this.patchedLayers.get(layerClass);
|
||||
patchedLayer.renderLayer(entity, entitypatch, layer, poseStack, buffer, packedLight, poses, bob, f2, f7, partialTicks);
|
||||
}
|
||||
}
|
||||
|
||||
for (PatchedLayer<E, T, M, ? extends RenderLayer<E, M>> patchedLayer : this.customLayers) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
PatchedLayer raw = patchedLayer;
|
||||
raw.renderLayer(entity, entitypatch, null, poseStack, buffer, packedLight, poses, bob, f2, f7, partialTicks);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getOverlayCoord(E entity, T entitypatch, float partialTicks) {
|
||||
// RIG Phase 2.5 : EntityDecorations.modifyOverlay strippé — utilise
|
||||
// les defaults vanilla (hurt flash + death fade).
|
||||
int initU = 0;
|
||||
int initV = OverlayTexture.v(entity.hurtTime > 0 || entity.deathTime > 0);
|
||||
return OverlayTexture.pack(initU, initV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mulPoseStack(PoseStack poseStack, Armature armature, E entity, T entitypatch, float partialTicks) {
|
||||
super.mulPoseStack(poseStack, armature, entity, entitypatch, partialTicks);
|
||||
|
||||
if (entity.isCrouching()) {
|
||||
poseStack.translate(0.0D, 0.15D, 0.0D);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPatchedLayer(Class<?> originalLayerClass, PatchedLayer<E, T, M, ? extends RenderLayer<E, M>> patchedLayer) {
|
||||
this.patchedLayers.putIfAbsent(originalLayerClass, patchedLayer);
|
||||
}
|
||||
|
||||
public void addCustomLayer(PatchedLayer<E, T, M, ? extends RenderLayer<E, M>> patchedLayer) {
|
||||
this.customLayers.add(patchedLayer);
|
||||
}
|
||||
|
||||
protected float getDefaultLayerHeightCorrection() {
|
||||
return 1.15F;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.render;
|
||||
|
||||
import net.minecraft.client.model.PlayerModel;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.mesh.HumanoidMesh;
|
||||
import com.tiedup.remake.rig.mesh.Meshes;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.patch.ClientPlayerPatch;
|
||||
|
||||
/**
|
||||
* Renderer RIG patché pour les joueurs (local + remote).
|
||||
*
|
||||
* <p>Dispatch slim vs default via {@link #getMeshProvider} — suit la règle
|
||||
* vanilla {@code entity.getModelName().equals("slim")} → Alex mesh, sinon
|
||||
* Steve/biped mesh.</p>
|
||||
*
|
||||
* <p>Override de {@link #prepareModel} pour propager les flags de visibilité
|
||||
* des {@link PlayerModel#head head / hat / jacket / sleeves / pants} vers les
|
||||
* {@link HumanoidMesh} parts correspondants — nécessaire pour que les
|
||||
* toggles client MC ("Cape", "Left Sleeve" etc.) fonctionnent avec le rendu
|
||||
* RIG.</p>
|
||||
*
|
||||
* <p><b>Layers skipés vs EF</b> (Phase 2.5, à rework Phase 3) :</p>
|
||||
* <ul>
|
||||
* <li>{@code ArrowLayer} — flèches plantées dans le player (V3-REW-09)</li>
|
||||
* <li>{@code BeeStingerLayer} — dards d'abeille plantés (V3-REW-09)</li>
|
||||
* <li>{@code CapeLayer} — cape vanilla (V3-REW-08, compat skin layers)</li>
|
||||
* <li>{@code PlayerItemInHandLayer} — items en main (version player-spécifique,
|
||||
* distincte de {@link net.minecraft.client.renderer.entity.layers.ItemInHandLayer}) —
|
||||
* le layer vanilla continue de dessiner jusqu'à la Phase 3</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Fork conceptuel de {@code yesman.epicfight.client.renderer.patched.entity.PPlayerRenderer}
|
||||
* (EF 60 LOC) — signatures identiques mais sans patched layers combat EF.</p>
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class TiedUpPlayerRenderer extends PHumanoidRenderer<AbstractClientPlayer, ClientPlayerPatch<AbstractClientPlayer>, PlayerModel<AbstractClientPlayer>, PlayerRenderer, HumanoidMesh> {
|
||||
|
||||
public TiedUpPlayerRenderer(EntityRendererProvider.Context context, EntityType<?> entityType) {
|
||||
super(Meshes.BIPED, context, entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareModel(HumanoidMesh mesh, AbstractClientPlayer entity, ClientPlayerPatch<AbstractClientPlayer> entitypatch, PlayerRenderer renderer) {
|
||||
super.prepareModel(mesh, entity, entitypatch, renderer);
|
||||
|
||||
// Renderer vanilla : lit les player options (modelParts toggle UI)
|
||||
// pour mettre à jour model.{head,hat,jacket,leftArm,...}.visible.
|
||||
renderer.setModelProperties(entity);
|
||||
PlayerModel<AbstractClientPlayer> model = renderer.getModel();
|
||||
|
||||
// Propage vers le mesh RIG : chaque part peut être hidden indépendamment.
|
||||
// HumanoidMesh peut avoir des parts null (legacy biped sans sleeves) —
|
||||
// on protège, sinon NPE au runtime sur les meshes partiels.
|
||||
setHiddenSafe(mesh.head, !model.head.visible);
|
||||
setHiddenSafe(mesh.hat, !model.hat.visible);
|
||||
setHiddenSafe(mesh.jacket, !model.jacket.visible);
|
||||
setHiddenSafe(mesh.torso, !model.body.visible);
|
||||
setHiddenSafe(mesh.leftArm, !model.leftArm.visible);
|
||||
setHiddenSafe(mesh.leftLeg, !model.leftLeg.visible);
|
||||
setHiddenSafe(mesh.leftPants, !model.leftPants.visible);
|
||||
setHiddenSafe(mesh.leftSleeve, !model.leftSleeve.visible);
|
||||
setHiddenSafe(mesh.rightArm, !model.rightArm.visible);
|
||||
setHiddenSafe(mesh.rightLeg, !model.rightLeg.visible);
|
||||
setHiddenSafe(mesh.rightPants, !model.rightPants.visible);
|
||||
setHiddenSafe(mesh.rightSleeve, !model.rightSleeve.visible);
|
||||
}
|
||||
|
||||
private static void setHiddenSafe(SkinnedMesh.SkinnedMeshPart part, boolean hidden) {
|
||||
if (part != null) {
|
||||
part.setHidden(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetAccessor<HumanoidMesh> getMeshProvider(ClientPlayerPatch<AbstractClientPlayer> entitypatch) {
|
||||
return entitypatch.getOriginal().getModelName().equals("slim") ? Meshes.ALEX : Meshes.BIPED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetAccessor<HumanoidMesh> getDefaultMesh() {
|
||||
return Meshes.BIPED;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@
|
||||
"refmap": "tiedup-rig.refmap.json",
|
||||
"mixins": [],
|
||||
"client": [
|
||||
"client.MixinAgeableListModel"
|
||||
"client.MixinAgeableListModel",
|
||||
"client.MixinEntityRenderer",
|
||||
"client.MixinLivingEntityRenderer"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
Reference in New Issue
Block a user