From e3e67b5dd8ddaf2c4a880d521d5864436d9e3f29 Mon Sep 17 00:00:00 2001 From: ZigyTheBird <105124180+ZigyTheBird@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:57:20 +0330 Subject: [PATCH 1/3] Major refactor moving things to the render thread --- .../animation/AnimationController.java | 24 +++- .../animation/AnimationData.java | 112 +++++++++++++++--- .../layered/modifier/AdjustmentModifier.java | 16 ++- .../playeranimcore/data/DataTicket.java | 88 ++++++++++++++ .../playeranim/accessors/IAnimatedAvatar.java | 7 ++ .../accessors/IAvatarAnimationState.java | 9 +- .../accessors/ILevelRenderState.java | 7 ++ .../animation/AvatarAnimManager.java | 91 +++++--------- .../playeranim/mixin/AvatarMixin.java | 23 +++- .../mixin/AvatarRenderStateMixin.java | 22 ++-- .../playeranim/mixin/AvatarRendererMixin.java | 25 ++-- .../mixin/LevelRenderStateMixin.java | 29 +++++ .../playeranim/mixin/LevelRendererMixin.java | 57 +++++++++ .../mixin/LivingEntityRendererMixin.java | 11 +- .../mixin/firstPerson/LevelRendererMixin.java | 3 +- .../player_animation_library.mixins.json | 2 + 16 files changed, 400 insertions(+), 126 deletions(-) create mode 100644 core/src/main/java/com/zigythebird/playeranimcore/data/DataTicket.java create mode 100644 minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/ILevelRenderState.java create mode 100644 minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRenderStateMixin.java create mode 100644 minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java index d0885d48..7df30293 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java @@ -881,20 +881,28 @@ public PlayerAnimBone get3DTransform(@NotNull PlayerAnimBone bone) { return IAnimation.DEFAULT_FIRST_PERSON_CONFIG; } - public void setFirstPersonMode(FirstPersonMode mode) { + public AnimationController setFirstPersonMode(FirstPersonMode mode) { firstPersonMode = (controller) -> mode; + + return this; } - public void setFirstPersonModeHandler(Function modeHandler) { + public AnimationController setFirstPersonModeHandler(Function modeHandler) { firstPersonMode = modeHandler; + + return this; } - public void setFirstPersonConfiguration(FirstPersonConfiguration config) { + public AnimationController setFirstPersonConfiguration(FirstPersonConfiguration config) { firstPersonConfiguration = (controller) -> config; + + return this; } - public void setFirstPersonConfigurationHandler(Function configHandler) { + public AnimationController setFirstPersonConfigurationHandler(Function configHandler) { firstPersonConfiguration = configHandler; + + return this; } @Override @@ -906,7 +914,12 @@ public void tick(AnimationData state) { } if (!modifiers.isEmpty()) modifiers.getFirst().tick(state); - else if (this.animationState == State.RUNNING) tick += 1; + else { + handleAnimation(state); + if (this.animationState == State.RUNNING) { + tick += 1; + } + } } @Override @@ -1058,6 +1071,7 @@ private InternalAnimationAccessor(AnimationController controller) { @Override public void tick(AnimationData state) { + this.anim.handleAnimation(state); if (this.anim.animationState == State.RUNNING) this.anim.tick += 1; } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java index 389b1c6c..f42c112d 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java @@ -24,52 +24,132 @@ package com.zigythebird.playeranimcore.animation; +import com.zigythebird.playeranimcore.PlayerAnimLib; +import com.zigythebird.playeranimcore.data.DataTicket; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +//TODO Move to the data folder next MC breaking change public class AnimationData { - private float velocity; - private float partialTick; + public static final DataTicket PARTIAL_TICK = DataTicket.create("partial_tick", Float.class); + public static final DataTicket VELOCITY = DataTicket.create("velocity", Float.class); + public static final DataTicket IS_FIRST_PERSON_PASS = DataTicket.create("first_person_pass", Boolean.class); + + private final Map, Object> data; + + public AnimationData() { + this.data = new Reference2ObjectOpenHashMap<>(); + } - public AnimationData(float velocity, float partialTick) { - this.velocity = velocity; - this.partialTick = partialTick; + AnimationData(Map, Object> data) { + this.data = data; } /** * Gets the fractional value of the current game tick that has passed in rendering */ public float getPartialTick() { - return this.partialTick; + return this.getOrDefaultData(PARTIAL_TICK, 0f); } - /** - * The player's velocity. - */ public float getVelocity() { - return this.velocity; + return this.getOrDefaultData(VELOCITY, 0f); + } + + public boolean isFirstPersonPass() { + return this.getOrDefaultData(IS_FIRST_PERSON_PASS, false); } /** - * Helper to determine if the player is moving. + * Helper to determine if the entity is moving. */ public boolean isMoving() { - return this.velocity > 0.015F; + return this.getVelocity() > 0.015F; } /** * The less strict counterpart of the method above. */ public boolean isMovingLenient() { - return this.velocity > 1.0E-6F; + return this.getVelocity() > 1.0E-6F; } public void setVelocity(float velocity) { - this.velocity = velocity; + this.addData(VELOCITY, velocity); } public void setPartialTick(float partialTick) { - this.partialTick = partialTick; + this.addData(PARTIAL_TICK, partialTick); + } + + /** + * Add data to the RenderState + * @param dataTicket The DataTicket identifying the data + * @param data The associated data + */ + public void addData(DataTicket dataTicket, @Nullable D data) { + this.data.put(dataTicket, data); + } + + /** + * @return Whether the RenderState has data associated with the given {@link DataTicket} + */ + public boolean hasData(DataTicket dataTicket) { + return this.data.containsKey(dataTicket); + } + + /** + * Get previously set data on the RenderState by its associated {@link DataTicket}. + *

+ * Note that you should NOT be attempting to retrieve data you don't know exists.
+ * Use {@link #hasData(DataTicket)} if unsure + * + * @param dataTicket The DataTicket associated with the data + * @return The data contained on this RenderState, null if the data is set to null, or an exception if the data doesn't exist + */ + public @Nullable D getData(DataTicket dataTicket) { + Object data = this.data.get(dataTicket); + + if (data == null && !hasData(dataTicket)) + throw new IllegalArgumentException("Attempted to retrieve data from AnimationData that does not exist. Check your code!"); + + try { + return (D)data; + } + catch (ClassCastException ex) { + PlayerAnimLib.LOGGER.error("Attempted to retrieve incorrectly typed data from AnimationData. Possibly a mod or DataTicket conflict? Expected: {}, found data type {}", dataTicket, data.getClass().getName(), ex); + + throw ex; + } + } + + /** + * Get previously set data by its associated {@link DataTicket}, + * or a default value if the data does not exist + * + * @param dataTicket The DataTicket associated with the data + * @param defaultValue The fallback value if no data has been set for the given DataTicket + * @return The data contained on this RenderState, null if the data is set to null, or {@code defaultValue} if not present + */ + public D getOrDefaultData(DataTicket dataTicket, @Nullable D defaultValue) { + Object data = this.data.get(dataTicket); + + if (data == null && !hasData(dataTicket)) + return defaultValue; + + try { + return (D)data; + } + catch (ClassCastException ex) { + PlayerAnimLib.LOGGER.error("Attempted to retrieve incorrectly typed data from AnimationData. Possibly a mod or DataTicket conflict? Expected: {}, found data type {}", dataTicket, data.getClass().getName(), ex); + + return defaultValue; + } } public AnimationData copy() { - return new AnimationData(velocity, partialTick); + return new AnimationData(new Reference2ObjectOpenHashMap<>(this.data)); } } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/modifier/AdjustmentModifier.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/modifier/AdjustmentModifier.java index c3d35c5e..c7907bec 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/modifier/AdjustmentModifier.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/modifier/AdjustmentModifier.java @@ -32,6 +32,7 @@ import java.util.Objects; import java.util.Optional; +import java.util.function.BiFunction; import java.util.function.Function; /** @@ -135,17 +136,22 @@ public String toString() { public boolean fadeOut = true; /// Whether the adjustment should be applied at all public boolean enabled = true; - private float tickDelta; + private AnimationData data; - protected Function> source; + protected BiFunction> source; public AdjustmentModifier(Function> source) { + this.source = (name, data) -> source.apply(name); + } + + public AdjustmentModifier(BiFunction> source) { this.source = source; } @Override public void tick(AnimationData state) { super.tick(state); + this.data = state; if (remainingFadeout > 0) { remainingFadeout -= 1; @@ -158,7 +164,7 @@ public void tick(AnimationData state) { @Override public void setupAnim(AnimationData state) { super.setupAnim(state); - this.tickDelta = state.getPartialTick(); + this.data = state; } protected int instructedFadeout = 0; @@ -206,9 +212,9 @@ public PlayerAnimBone get3DTransform(@NotNull PlayerAnimBone bone) { return super.get3DTransform(bone); } - Optional partModifier = source.apply(bone.getName()); + Optional partModifier = source.apply(bone.getName(), data); - float fade = getFadeIn() * getFadeOut(tickDelta); + float fade = getFadeIn() * getFadeOut(data.getPartialTick()); if (partModifier.isPresent()) { super.get3DTransform(bone); transformBone(bone, partModifier.get(), fade); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/data/DataTicket.java b/core/src/main/java/com/zigythebird/playeranimcore/data/DataTicket.java new file mode 100644 index 00000000..fb13b7c2 --- /dev/null +++ b/core/src/main/java/com/zigythebird/playeranimcore/data/DataTicket.java @@ -0,0 +1,88 @@ +/* + * MIT License + * + * Copyright (c) 2024 GeckoLib + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.zigythebird.playeranimcore.data; + +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.util.Map; +import java.util.Objects; + +/** + * Ticket object to define a typed data object + */ +public class DataTicket { + static final Map, String>, DataTicket> IDENTITY_CACHE = new Object2ObjectOpenHashMap<>(); + + private final String id; + private final Class objectType; + + /** + * @see #create(String, Class) + */ + DataTicket(String id, Class objectType) { + this.id = id; + this.objectType = objectType; + } + + /** + * Create a new DataTicket for a given ID and object type + * Please include a namespace in your ID like namespace mod_id:name to avoid conflicts with other mods. + *

+ * This DataTicket should then be stored statically somewhere and re-used. + */ + public static DataTicket create(String id, Class objectType) { + return (DataTicket)IDENTITY_CACHE.computeIfAbsent(Pair.of(objectType, id), pair -> new DataTicket<>(id, objectType)); + } + + public String id() { + return this.id; + } + + public Class objectType() { + return this.objectType; + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.objectType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + + if (!(obj instanceof DataTicket other)) + return false; + + return this.objectType == other.objectType && this.id.equals(other.id); + } + + @Override + public String toString() { + return "DataTicket{" + this.id + ": " + this.objectType.getName() + "}"; + } +} \ No newline at end of file diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAnimatedAvatar.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAnimatedAvatar.java index e1fa6e39..12795092 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAnimatedAvatar.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAnimatedAvatar.java @@ -1,10 +1,17 @@ package com.zigythebird.playeranim.accessors; import com.zigythebird.playeranim.animation.AvatarAnimManager; +import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.animation.layered.IAnimation; import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.ApiStatus; public interface IAnimatedAvatar { AvatarAnimManager playerAnimLib$getAnimManager(); IAnimation playerAnimLib$getAnimation(Identifier id); + + AnimationData playerAnimlib$getAnimData(); + + @ApiStatus.Internal + boolean playerAnimLib$isAwaitingTick(); } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAvatarAnimationState.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAvatarAnimationState.java index cb0d5b49..b9a34340 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAvatarAnimationState.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAvatarAnimationState.java @@ -1,15 +1,20 @@ package com.zigythebird.playeranim.accessors; import com.zigythebird.playeranim.animation.AvatarAnimManager; +import com.zigythebird.playeranimcore.animation.AnimationData; +import org.jetbrains.annotations.ApiStatus; /** * Extension of PlayerRenderState */ public interface IAvatarAnimationState { boolean playerAnimLib$isFirstPersonPass(); - void playerAnimLib$setFirstPersonPass(boolean value); - // AnimationApplier animationApplier + @ApiStatus.Internal void playerAnimLib$setAnimManager(AvatarAnimManager manager); AvatarAnimManager playerAnimLib$getAnimManager(); + + @ApiStatus.Internal + void playerAnimLib$setAnimData(AnimationData data); + AnimationData playerAnimLib$getAnimData(); } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/ILevelRenderState.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/ILevelRenderState.java new file mode 100644 index 00000000..e0d56d2b --- /dev/null +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/ILevelRenderState.java @@ -0,0 +1,7 @@ +package com.zigythebird.playeranim.accessors; + +import java.util.List; + +public interface ILevelRenderState { + List playerAnimLib$getAnimatedAvatarsToTick(); +} diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java index 98dc9aa4..078baaab 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java @@ -1,6 +1,6 @@ package com.zigythebird.playeranim.animation; -import com.zigythebird.playeranim.accessors.IAnimatedAvatar; +import com.zigythebird.playeranim.accessors.IAvatarAnimationState; import com.zigythebird.playeranim.util.RenderUtil; import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.animation.layered.AnimationStack; @@ -10,8 +10,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.PartPose; -import net.minecraft.world.entity.Avatar; -import net.minecraft.world.phys.Vec3; +import net.minecraft.client.renderer.entity.state.AvatarRenderState; import org.jetbrains.annotations.ApiStatus; /** @@ -20,90 +19,56 @@ * Generally speaking, a single working-instance of a player will have a single instance of {@code PlayerAnimManager} associated with it */ public class AvatarAnimManager extends AnimationStack { - private final Avatar avatar; - private float lastUpdateTime; - private boolean isFirstTick = true; - private float tickDelta; - - public AvatarAnimManager(Avatar avatar) { - this.avatar = avatar; - } - /** - * Tick and apply transformations to the model based on the current state of the {@link com.zigythebird.playeranimcore.animation.layered.AnimationContainer} - * - * @param playerAnimManager The PlayerAnimManager instance being used for this animation processor - * @param state An {@link AnimationData} instance applied to this render frame - */ - public void tickAnimation(AnimationStack playerAnimManager, AnimationData state) { - playerAnimManager.getLayers().removeIf(pair -> pair.right() == null || pair.right().canRemove()); - for (Pair pair : playerAnimManager.getLayers()) { - IAnimation animation = pair.right(); - - if (animation.isActive()) - animation.setupAnim(state.copy()); - } - finishFirstTick(); - } + public AvatarAnimManager() {} public float getLastUpdateTime() { return this.lastUpdateTime; } + @ApiStatus.Internal public void updatedAt(float updateTime) { this.lastUpdateTime = updateTime; } - public boolean isFirstTick() { - return this.isFirstTick; - } - - protected void finishFirstTick() { - this.isFirstTick = false; - } - - public float getTickDelta() { - return this.tickDelta; - } - - /** - * If you touch this, you're a horrible person. - */ - @ApiStatus.Internal - public void setTickDelta(float tickDelta) { - this.tickDelta = tickDelta; - } - public void updatePart(ModelPart part, PlayerAnimBone bone) { PartPose initialPose = part.getInitialPose(); bone = this.get3DTransform(bone); RenderUtil.translatePartToBone(part, bone, initialPose); } - public void handleAnimations(float partialTick, boolean fullTick) { - Vec3 velocity = avatar.getDeltaMovement(); - - AvatarAnimManager animatableManager = ((IAnimatedAvatar)avatar).playerAnimLib$getAnimManager(); - int currentTick = avatar.tickCount; + @ApiStatus.Internal + public void handleAnimations(IAvatarAnimationState state, boolean setupAnim) { + if (state instanceof AvatarRenderState avatarRenderState) { + float currentFrameTime = avatarRenderState.ageInTicks; - float currentFrameTime = currentTick + partialTick; + if (currentFrameTime == this.getLastUpdateTime()) + return; - AnimationData animationData = new AnimationData((float) ((Math.abs(velocity.x) + Math.abs(velocity.z)) / 2f), partialTick); + AnimationData animationData = state.playerAnimLib$getAnimData(); + // I have to do this due to floating-point error nonsense + animationData.setPartialTick(currentFrameTime - (int)currentFrameTime); - if (fullTick) animatableManager.tick(animationData.copy()); + if (!Minecraft.getInstance().isPaused()) { + for (int i = 0; i < (int)currentFrameTime - (int)this.getLastUpdateTime(); i++) + this.tick(animationData.copy()); - if (!animatableManager.isFirstTick() && currentFrameTime == animatableManager.getLastUpdateTime()) - return; + this.updatedAt(currentFrameTime); + } - if (!Minecraft.getInstance().isPaused()) { - animatableManager.updatedAt(currentFrameTime); + if (setupAnim) + this.setupAnimation(animationData); } - - this.tickAnimation(animatableManager, animationData); } - public Avatar getAvatar() { - return avatar; + protected void setupAnimation(AnimationData state) { + this.getLayers().removeIf(pair -> pair.right() == null || pair.right().canRemove()); + for (Pair pair : this.getLayers()) { + IAnimation animation = pair.right(); + + if (animation.isActive()) + animation.setupAnim(state.copy()); + } } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarMixin.java index fec1b0bd..c0306f06 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarMixin.java @@ -28,6 +28,7 @@ import com.zigythebird.playeranim.animation.AvatarAnimManager; import com.zigythebird.playeranim.api.PlayerAnimationAccess; import com.zigythebird.playeranim.api.PlayerAnimationFactory; +import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.animation.layered.IAnimation; import net.minecraft.resources.Identifier; import net.minecraft.world.entity.Avatar; @@ -50,6 +51,10 @@ public abstract class AvatarMixin extends LivingEntity implements IAnimatedAvata private final Map playerAnimLib$modAnimationData = new HashMap<>(); @Unique private final AvatarAnimManager playerAnimLib$animationManager = playerAnimLib$createAnimationStack(); + @Unique + private final AnimationData playerAnimLib$data = new AnimationData(); + @Unique + private boolean playerAnimLib$awaitingTick = false; protected AvatarMixin(EntityType entityType, Level level) { super(entityType, level); @@ -57,7 +62,7 @@ protected AvatarMixin(EntityType entityType, Level level @Unique private AvatarAnimManager playerAnimLib$createAnimationStack() { - AvatarAnimManager manager = new AvatarAnimManager((Avatar) (Object) this); + AvatarAnimManager manager = new AvatarAnimManager(); PlayerAnimationFactory.ANIMATION_DATA_FACTORY.prepareAnimations((Avatar) (Object) this, manager, playerAnimLib$modAnimationData); PlayerAnimationAccess.REGISTER_ANIMATION_EVENT.invoker().registerAnimation((Avatar) (Object) this, manager); return manager; @@ -74,6 +79,18 @@ protected AvatarMixin(EntityType entityType, Level level return null; } + @Override + public AnimationData playerAnimlib$getAnimData() { + return this.playerAnimLib$data; + } + + @Override + public boolean playerAnimLib$isAwaitingTick() { + boolean value = this.playerAnimLib$awaitingTick; + this.playerAnimLib$awaitingTick = false; + return value; + } + @Intrinsic @Override public void tick() { @@ -81,8 +98,8 @@ public void tick() { } @SuppressWarnings({"MixinAnnotationTarget", "UnresolvedMixinReference"}) - @Inject(method = {"tick", "method_5773"}, at = @At("TAIL"), remap = false) + @Inject(method = {"tick", "method_5773"}, at = @At("HEAD"), remap = false) private void tick(CallbackInfo ci) { - this.playerAnimLib$animationManager.handleAnimations(0, true); + this.playerAnimLib$awaitingTick = true; } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRenderStateMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRenderStateMixin.java index d6b118a7..7688c25d 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRenderStateMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRenderStateMixin.java @@ -2,6 +2,7 @@ import com.zigythebird.playeranim.accessors.IAvatarAnimationState; import com.zigythebird.playeranim.animation.AvatarAnimManager; +import com.zigythebird.playeranimcore.animation.AnimationData; import net.minecraft.client.renderer.entity.state.AvatarRenderState; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Mixin; @@ -10,19 +11,14 @@ @Mixin(AvatarRenderState.class) public class AvatarRenderStateMixin implements IAvatarAnimationState { @Unique - boolean playerAnimLib$isFirstPersonPass = false; + AvatarAnimManager playerAnimLib$avatarAnimManager = null; @Unique - AvatarAnimManager playerAnimLib$avatarAnimManager = null; + AnimationData playerAnimLib$data = null; @Override public boolean playerAnimLib$isFirstPersonPass() { - return playerAnimLib$isFirstPersonPass; - } - - @Override - public void playerAnimLib$setFirstPersonPass(boolean value) { - playerAnimLib$isFirstPersonPass = value; + return this.playerAnimLib$data.isFirstPersonPass(); } @Override @@ -34,5 +30,15 @@ public class AvatarRenderStateMixin implements IAvatarAnimationState { public @NotNull AvatarAnimManager playerAnimLib$getAnimManager() { return this.playerAnimLib$avatarAnimManager; } + + @Override + public void playerAnimLib$setAnimData(AnimationData data) { + this.playerAnimLib$data = data; + } + + @Override + public AnimationData playerAnimLib$getAnimData() { + return this.playerAnimLib$data; + } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRendererMixin.java index cd799c28..d0b99bb8 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRendererMixin.java @@ -26,33 +26,26 @@ import com.zigythebird.playeranim.accessors.IAnimatedAvatar; import com.zigythebird.playeranim.accessors.IAvatarAnimationState; -import com.zigythebird.playeranim.animation.AvatarAnimManager; -import net.minecraft.client.Minecraft; -import net.minecraft.client.model.player.PlayerModel; -import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.renderer.entity.EntityRendererProvider; -import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import com.zigythebird.playeranimcore.animation.AnimationData; import net.minecraft.client.renderer.entity.player.AvatarRenderer; import net.minecraft.client.renderer.entity.state.AvatarRenderState; import net.minecraft.world.entity.Avatar; +import net.minecraft.world.phys.Vec3; 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; @Mixin(value = AvatarRenderer.class, priority = 2000) -public abstract class AvatarRendererMixin extends LivingEntityRenderer { - public AvatarRendererMixin(EntityRendererProvider.Context context, PlayerModel model, float shadowRadius) { - super(context, model, shadowRadius); - } - +public abstract class AvatarRendererMixin { @Inject(method = "extractRenderState(Lnet/minecraft/world/entity/Avatar;Lnet/minecraft/client/renderer/entity/state/AvatarRenderState;F)V", at = @At("HEAD")) private void modifyRenderState(Avatar avatar, AvatarRenderState avatarRenderState, float f, CallbackInfo ci) { - if (avatar instanceof IAnimatedAvatar abstractClientPlayer) { - AvatarAnimManager animation = abstractClientPlayer.playerAnimLib$getAnimManager(); - animation.setTickDelta(f); - - ((IAvatarAnimationState)avatarRenderState).playerAnimLib$setAnimManager(animation); + if (avatar instanceof IAnimatedAvatar animatedAvatar && avatarRenderState instanceof IAvatarAnimationState avatarAnimationState) { + avatarAnimationState.playerAnimLib$setAnimManager(animatedAvatar.playerAnimLib$getAnimManager()); + Vec3 velocity = avatar.getDeltaMovement(); + AnimationData data = animatedAvatar.playerAnimlib$getAnimData().copy(); + data.setVelocity((float) ((Math.abs(velocity.x) + Math.abs(velocity.z)) / 2f)); + avatarAnimationState.playerAnimLib$setAnimData(data); } } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRenderStateMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRenderStateMixin.java new file mode 100644 index 00000000..b249d5b8 --- /dev/null +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRenderStateMixin.java @@ -0,0 +1,29 @@ +package com.zigythebird.playeranim.mixin; + +import com.zigythebird.playeranim.accessors.IAvatarAnimationState; +import com.zigythebird.playeranim.accessors.ILevelRenderState; +import net.minecraft.client.renderer.state.LevelRenderState; +import org.spongepowered.asm.mixin.Mixin; +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 java.util.ArrayList; +import java.util.List; + +@Mixin(LevelRenderState.class) +public class LevelRenderStateMixin implements ILevelRenderState { + @Unique + private final List playerAnimLib$animatedAvatarsToTick = new ArrayList<>(); + + @Override + public List playerAnimLib$getAnimatedAvatarsToTick() { + return playerAnimLib$animatedAvatarsToTick; + } + + @Inject(method = "reset", at = @At("TAIL")) + private void reset(CallbackInfo ci) { + this.playerAnimLib$animatedAvatarsToTick.clear(); + } +} diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java new file mode 100644 index 00000000..bf46fdab --- /dev/null +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java @@ -0,0 +1,57 @@ +package com.zigythebird.playeranim.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.blaze3d.vertex.PoseStack; +import com.zigythebird.playeranim.accessors.IAnimatedAvatar; +import com.zigythebird.playeranim.accessors.IAvatarAnimationState; +import com.zigythebird.playeranim.accessors.ILevelRenderState; +import net.minecraft.client.Camera; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.entity.state.EntityRenderState; +import net.minecraft.client.renderer.state.LevelRenderState; +import net.minecraft.world.TickRateManager; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LevelRenderer.class) +public abstract class LevelRendererMixin { + @Shadow protected abstract EntityRenderState extractEntity(Entity entity, float partialTick); + + @Shadow @Final private Minecraft minecraft; + + @Inject(method = "extractVisibleEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) + private void doNotTickIfRendering(Camera camera, Frustum frustum, DeltaTracker deltaTracker, LevelRenderState renderState, CallbackInfo ci, @Local Entity entity) { + //When the method is called it will return false from now + //So we won't do an extra tick when the renderer is already about to do one + if (entity instanceof IAnimatedAvatar animatedAvatar) animatedAvatar.playerAnimLib$isAwaitingTick(); + } + + @Inject(method = "extractVisibleEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;shouldRender(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/client/renderer/culling/Frustum;DDD)Z")) + private void tickIfNotRendering(Camera camera, Frustum frustum, DeltaTracker deltaTracker, LevelRenderState renderState, CallbackInfo ci, @Local Entity entity) { + if (entity instanceof IAnimatedAvatar animatedAvatar && animatedAvatar.playerAnimLib$isAwaitingTick() + && renderState instanceof ILevelRenderState levelRenderState) { + TickRateManager tickRateManager = this.minecraft.level.tickRateManager(); + if (this.extractEntity(entity, deltaTracker.getGameTimeDeltaPartialTick(!tickRateManager.isEntityFrozen(entity))) + instanceof IAvatarAnimationState avatarAnimationState) + levelRenderState.playerAnimLib$getAnimatedAvatarsToTick().add(avatarAnimationState); + } + } + + @Inject(method = "submitEntities", at = @At(value = "HEAD")) + private void tickAnimatedAvatars(PoseStack poseStack, LevelRenderState renderState, SubmitNodeCollector nodeCollector, CallbackInfo ci) { + if (renderState instanceof ILevelRenderState levelRenderState) { + for (IAvatarAnimationState state : levelRenderState.playerAnimLib$getAnimatedAvatarsToTick()) { + state.playerAnimLib$getAnimManager().handleAnimations(state, false); + } + } + } +} diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java index 57bbd04b..e46eea75 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java @@ -28,11 +28,8 @@ import com.zigythebird.playeranim.accessors.IAvatarAnimationState; import com.zigythebird.playeranim.util.RenderUtil; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; -import net.minecraft.client.model.EntityModel; -import net.minecraft.client.model.player.PlayerModel; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.entity.LivingEntityRenderer; -import net.minecraft.client.renderer.entity.state.AvatarRenderState; import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.renderer.state.CameraRenderState; import org.spongepowered.asm.mixin.Mixin; @@ -41,13 +38,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LivingEntityRenderer.class) -public class LivingEntityRendererMixin> { +public class LivingEntityRendererMixin { @Inject(method = "submit(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;Lnet/minecraft/client/renderer/state/CameraRenderState;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/LivingEntityRenderer;scale(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;)V")) private void doTranslations(S livingEntityRenderState, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState cameraRenderState, CallbackInfo ci) { - if (livingEntityRenderState instanceof AvatarRenderState playerRenderState) { - var animationPlayer = ((IAvatarAnimationState)playerRenderState).playerAnimLib$getAnimManager(); + if (livingEntityRenderState instanceof IAvatarAnimationState animationRenderState) { + var animationPlayer = ((IAvatarAnimationState)animationRenderState).playerAnimLib$getAnimManager(); if (animationPlayer != null && animationPlayer.isActive()) { - ((IAvatarAnimationState)playerRenderState).playerAnimLib$getAnimManager().handleAnimations(animationPlayer.getTickDelta(), false); + animationPlayer.handleAnimations(animationRenderState, true); poseStack.scale(-1.0F, -1.0F, 1.0F); //These are additive properties diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java index 8bc608de..89ce05c2 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java @@ -30,6 +30,7 @@ import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; import com.zigythebird.playeranim.accessors.IAnimatedAvatar; import com.zigythebird.playeranim.accessors.IAvatarAnimationState; +import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonMode; import net.minecraft.client.Camera; import net.minecraft.client.DeltaTracker; @@ -60,7 +61,7 @@ private boolean fakeThirdPersonMode(boolean original, @Local(argsOnly = true) Ca @Inject(method = "extractVisibleEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) private void setRenderStateToFirstPerson(Camera camera, Frustum frustum, DeltaTracker deltaTracker, LevelRenderState renderState, CallbackInfo ci, @Local Entity entity, @Local EntityRenderState entityRenderState, @Share("firstPerson") LocalBooleanRef isFirstPerson) { if (entity == camera.entity() && isFirstPerson.get()) { - ((IAvatarAnimationState) entityRenderState).playerAnimLib$setFirstPersonPass(true); + ((IAvatarAnimationState) entityRenderState).playerAnimLib$getAnimData().addData(AnimationData.IS_FIRST_PERSON_PASS, true); entityRenderState.shadowPieces.clear(); entityRenderState.shadowRadius = 0; } diff --git a/minecraft/common/src/main/resources/player_animation_library.mixins.json b/minecraft/common/src/main/resources/player_animation_library.mixins.json index 3a5f82e0..f49c40b5 100644 --- a/minecraft/common/src/main/resources/player_animation_library.mixins.json +++ b/minecraft/common/src/main/resources/player_animation_library.mixins.json @@ -10,6 +10,8 @@ "CapeModelAccessor", "ElytraLayerMixin", "ItemInHandLayerMixin", + "LevelRendererMixin", + "LevelRenderStateMixin", "LivingEntityRendererMixin", "PlayerCapeModelMixin", "PlayerModelMixin", From f3357c683099967fcf3cd742f6d9a33331013976 Mon Sep 17 00:00:00 2001 From: ZigyTheBird <105124180+ZigyTheBird@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:16:11 +0330 Subject: [PATCH 2/3] Fix issues with merge --- .../zigythebird/playeranimcore/animation/AnimationData.java | 4 ---- .../playeranim/mixin/firstPerson/LevelRendererMixin.java | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java index c08c121c..f42c112d 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java @@ -62,10 +62,6 @@ public boolean isFirstPersonPass() { return this.getOrDefaultData(IS_FIRST_PERSON_PASS, false); } - public boolean isFirstPersonPass() { - return this.isFirstPersonPass; - } - /** * Helper to determine if the entity is moving. */ diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java index 85d95bfc..7d99ffa4 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java @@ -29,8 +29,8 @@ import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; import com.zigythebird.playeranim.accessors.IAvatarAnimationState; +import com.zigythebird.playeranim.util.ClientUtil; import com.zigythebird.playeranimcore.animation.AnimationData; -import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonMode; import net.minecraft.client.Camera; import net.minecraft.client.DeltaTracker; import net.minecraft.client.renderer.LevelRenderer; From 642138e0b5f9a7356ee3acca70e59f24e916e647 Mon Sep 17 00:00:00 2001 From: ZigyTheBird <105124180+ZigyTheBird@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:27:22 +0330 Subject: [PATCH 3/3] Fix small jitter issues --- .../zigythebird/playeranim/animation/AvatarAnimManager.java | 5 ++--- .../com/zigythebird/playeranim/mixin/LevelRendererMixin.java | 2 +- .../playeranim/mixin/LivingEntityRendererMixin.java | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java index 078baaab..1c539741 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java @@ -39,7 +39,7 @@ public void updatePart(ModelPart part, PlayerAnimBone bone) { } @ApiStatus.Internal - public void handleAnimations(IAvatarAnimationState state, boolean setupAnim) { + public void handleAnimations(IAvatarAnimationState state) { if (state instanceof AvatarRenderState avatarRenderState) { float currentFrameTime = avatarRenderState.ageInTicks; @@ -57,8 +57,7 @@ public void handleAnimations(IAvatarAnimationState state, boolean setupAnim) { this.updatedAt(currentFrameTime); } - if (setupAnim) - this.setupAnimation(animationData); + this.setupAnimation(animationData); } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java index bf46fdab..e1a937e0 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java @@ -50,7 +50,7 @@ private void tickIfNotRendering(Camera camera, Frustum frustum, DeltaTracker del private void tickAnimatedAvatars(PoseStack poseStack, LevelRenderState renderState, SubmitNodeCollector nodeCollector, CallbackInfo ci) { if (renderState instanceof ILevelRenderState levelRenderState) { for (IAvatarAnimationState state : levelRenderState.playerAnimLib$getAnimatedAvatarsToTick()) { - state.playerAnimLib$getAnimManager().handleAnimations(state, false); + state.playerAnimLib$getAnimManager().handleAnimations(state); } } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java index e46eea75..b1243452 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java @@ -44,7 +44,7 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm if (livingEntityRenderState instanceof IAvatarAnimationState animationRenderState) { var animationPlayer = ((IAvatarAnimationState)animationRenderState).playerAnimLib$getAnimManager(); if (animationPlayer != null && animationPlayer.isActive()) { - animationPlayer.handleAnimations(animationRenderState, true); + animationPlayer.handleAnimations(animationRenderState); poseStack.scale(-1.0F, -1.0F, 1.0F); //These are additive properties