From cc92a456afa5f505df2c8eee3b1a2f11edc1bae7 Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Fri, 27 Feb 2026 01:22:52 +0700 Subject: [PATCH 01/13] vec deserializer --- .../playeranimcore/PlayerAnimLib.java | 2 ++ .../playeranimcore/bones/PlayerAnimBone.java | 2 -- .../zigythebird/playeranimcore/math/Vec3f.java | 18 +++++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java b/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java index 5f21407b..a37518fa 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java @@ -7,6 +7,7 @@ import com.zigythebird.playeranimcore.loading.AnimationLoader; import com.zigythebird.playeranimcore.loading.KeyFrameLoader; import com.zigythebird.playeranimcore.loading.UniversalAnimLoader; +import com.zigythebird.playeranimcore.math.Vec3f; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,6 +20,7 @@ public class PlayerAnimLib { public static final Type ANIMATIONS_MAP_TYPE = new TypeToken>() {}.getType(); public static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(Vec3f.class, new Vec3f.Deserializer()) .registerTypeAdapter(Animation.Keyframes.class, new KeyFrameLoader()) .registerTypeAdapter(Animation.class, new AnimationLoader()) .registerTypeAdapter(ANIMATIONS_MAP_TYPE, new UniversalAnimLoader()) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/bones/PlayerAnimBone.java b/core/src/main/java/com/zigythebird/playeranimcore/bones/PlayerAnimBone.java index f1a3dcaf..67285827 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/bones/PlayerAnimBone.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/bones/PlayerAnimBone.java @@ -8,8 +8,6 @@ import com.zigythebird.playeranimcore.easing.EasingType; import com.zigythebird.playeranimcore.enums.Axis; import com.zigythebird.playeranimcore.enums.TransformType; -import com.zigythebird.playeranimcore.math.MathHelper; -import com.zigythebird.playeranimcore.math.Vec3f; import org.jetbrains.annotations.ApiStatus; import org.joml.Vector3f; diff --git a/core/src/main/java/com/zigythebird/playeranimcore/math/Vec3f.java b/core/src/main/java/com/zigythebird/playeranimcore/math/Vec3f.java index 6775e6ae..02992c5f 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/math/Vec3f.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/math/Vec3f.java @@ -1,5 +1,9 @@ package com.zigythebird.playeranimcore.math; +import com.google.gson.*; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Type; import java.util.Objects; public record Vec3f(float x, float y, float z) { @@ -29,12 +33,12 @@ public Vec3f add(Vec3f other) { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof Vec3f vec)) return false; - return Objects.equals(x, vec.x) && Objects.equals(y, vec.y) && Objects.equals(z, vec.z); + if (!(o instanceof Vec3f(float x1, float y1, float z1))) return false; + return Objects.equals(x, x1) && Objects.equals(y, y1) && Objects.equals(z, z1); } @Override - public String toString() { + public @NotNull String toString() { return "Vec3f[" + this.x + "; " + this.y + "; " + this.z + "]"; } @@ -42,4 +46,12 @@ public String toString() { public int hashCode() { return Objects.hash(x, y, z); } + + public static class Deserializer implements JsonDeserializer { + @Override + public Vec3f deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException { + JsonArray array = json.getAsJsonArray(); + return new Vec3f(array.get(0).getAsFloat(), array.get(1).getAsFloat(), array.get(2).getAsFloat()); + } + } } From 09b05e85baed917b991b14d98c8fb1fac3138634 Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Fri, 27 Feb 2026 01:46:59 +0700 Subject: [PATCH 02/13] cmm --- .../playeranimcore/PlayerAnimLib.java | 2 + .../playeranimcore/animation/Animation.java | 3 +- .../animation/AnimationController.java | 14 +- .../animation/CustomAnimationBone.java | 16 ++ .../HumanoidAnimationController.java | 2 +- .../bindings/PlatformModel.java | 4 + .../playeranimcore/bones/CustomBone.java | 26 ++++ .../playeranimcore/bones/PivotBone.java | 16 -- .../loading/AnimationLoader.java | 4 +- .../loading/UniversalAnimLoader.java | 13 +- .../network/AnimationBinary.java | 6 +- .../network/AnimationBinaryV6.java | 6 +- .../playeranimcore/network/NetworkUtils.java | 10 ++ .../playeranimcore/util/MatrixUtil.java | 4 +- .../playeranim/animation/MinecraftModel.java | 141 ++++++++++++++++++ .../animation/PlayerAnimationController.java | 19 ++- .../mixin/LivingEntityRendererMixin.java | 81 ++++++++++ .../player_animations/cmm_test.json | 40 +++++ .../player_animation_library.classtweaker | 1 + 19 files changed, 365 insertions(+), 43 deletions(-) create mode 100644 core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java create mode 100644 core/src/main/java/com/zigythebird/playeranimcore/bindings/PlatformModel.java create mode 100644 core/src/main/java/com/zigythebird/playeranimcore/bones/CustomBone.java delete mode 100644 core/src/main/java/com/zigythebird/playeranimcore/bones/PivotBone.java create mode 100644 minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java create mode 100644 minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json diff --git a/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java b/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java index a37518fa..d197a6fe 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java @@ -4,6 +4,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.zigythebird.playeranimcore.animation.Animation; +import com.zigythebird.playeranimcore.animation.CustomAnimationBone; import com.zigythebird.playeranimcore.loading.AnimationLoader; import com.zigythebird.playeranimcore.loading.KeyFrameLoader; import com.zigythebird.playeranimcore.loading.UniversalAnimLoader; @@ -20,6 +21,7 @@ public class PlayerAnimLib { public static final Type ANIMATIONS_MAP_TYPE = new TypeToken>() {}.getType(); public static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(CustomAnimationBone.class, new CustomAnimationBone.Deserializer()) .registerTypeAdapter(Vec3f.class, new Vec3f.Deserializer()) .registerTypeAdapter(Animation.Keyframes.class, new KeyFrameLoader()) .registerTypeAdapter(Animation.class, new AnimationLoader()) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/Animation.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/Animation.java index b93786fc..5944cfaa 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/Animation.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/Animation.java @@ -31,7 +31,6 @@ import com.zigythebird.playeranimcore.animation.keyframe.event.data.ParticleKeyframeData; import com.zigythebird.playeranimcore.animation.keyframe.event.data.SoundKeyframeData; import com.zigythebird.playeranimcore.loading.UniversalAnimLoader; -import com.zigythebird.playeranimcore.math.Vec3f; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,7 +43,7 @@ *

* Modifications or extensions of a compiled Animation are not supported, and therefore an instance of Animation is considered final and immutable */ -public record Animation(ExtraAnimationData data, float length, LoopType loopType, Map boneAnimations, Keyframes keyFrames, Map bones, Map parents) implements Supplier { +public record Animation(ExtraAnimationData data, float length, LoopType loopType, Map boneAnimations, Keyframes keyFrames, Map bones, Map parents) implements Supplier { public record Keyframes(SoundKeyframeData[] sounds, ParticleKeyframeData[] particles, CustomInstructionKeyframeData[] customInstructions) { @Override public int hashCode() { 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 9dce5f1c..f0eb30f3 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java @@ -24,6 +24,7 @@ package com.zigythebird.playeranimcore.animation; +import com.google.gson.JsonObject; import com.zigythebird.playeranimcore.PlayerAnimLib; import com.zigythebird.playeranimcore.animation.keyframe.*; import com.zigythebird.playeranimcore.animation.keyframe.event.CustomKeyFrameEvents; @@ -39,6 +40,7 @@ import com.zigythebird.playeranimcore.animation.layered.modifier.SpeedModifier; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonConfiguration; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonMode; +import com.zigythebird.playeranimcore.bindings.PlatformModel; import com.zigythebird.playeranimcore.bones.*; import com.zigythebird.playeranimcore.easing.EasingType; import com.zigythebird.playeranimcore.enums.PlayState; @@ -75,7 +77,7 @@ public abstract class AnimationController implements IAnimation { protected final Map bonePositions; protected final Map bones = new Object2ObjectOpenHashMap<>(); protected final Map activeBones = new Object2ObjectOpenHashMap<>(); - protected final Map pivotBones = new Object2ObjectOpenHashMap<>(); + protected final Map pivotBones = new Object2ObjectOpenHashMap<>(); protected Queue animationQueue = new LinkedList<>(); protected final MochaEngine molangRuntime; @@ -739,13 +741,15 @@ else if (pivotBones.containsKey(entry.getKey())) } this.pivotBones.clear(); - for (Map.Entry entry : currentAnimation.animation().bones().entrySet()) { - this.pivotBones.put(entry.getKey(), new PivotBone(entry.getKey(), entry.getValue())); + for (Map.Entry entry : currentAnimation.animation().bones().entrySet()) { + this.pivotBones.put(entry.getKey(), new CustomBone(entry.getKey(), entry.getValue().pivot(), loadCustomModel(entry.getValue().model()))); } this.postAnimationSetupConsumer.accept((name) -> bones.getOrDefault(name, null)); } + protected abstract @Nullable PlatformModel loadCustomModel(JsonObject model); + /** * Compute animation value for the given keyframes at the specified tick */ @@ -912,6 +916,10 @@ public void setupAnim(AnimationData state) { else process(state); } + public Map getPivotBones() { + return pivotBones; + } + public Vec3f getBonePosition(String name) { if (bonePositions.containsKey(name)) return bonePositions.get(name); if (pivotBones.containsKey(name)) return pivotBones.get(name).getPivot(); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java new file mode 100644 index 00000000..feb30406 --- /dev/null +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java @@ -0,0 +1,16 @@ +package com.zigythebird.playeranimcore.animation; + +import com.google.gson.*; +import com.zigythebird.playeranimcore.math.Vec3f; + +import java.lang.reflect.Type; + +public record CustomAnimationBone(Vec3f pivot, JsonObject model) { + public static class Deserializer implements JsonDeserializer { + @Override + public CustomAnimationBone deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + return new CustomAnimationBone(ctx.deserialize(obj.get("pivot"), Vec3f.class), obj.getAsJsonObject("model")); + } + } +} diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/HumanoidAnimationController.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/HumanoidAnimationController.java index 52d2b92a..259ab2ac 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/HumanoidAnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/HumanoidAnimationController.java @@ -34,7 +34,7 @@ import java.util.Map; import java.util.function.Function; -public class HumanoidAnimationController extends AnimationController { +public abstract class HumanoidAnimationController extends AnimationController { /** * Bone pivot point positions used to apply custom pivot point translations. */ diff --git a/core/src/main/java/com/zigythebird/playeranimcore/bindings/PlatformModel.java b/core/src/main/java/com/zigythebird/playeranimcore/bindings/PlatformModel.java new file mode 100644 index 00000000..83ffca04 --- /dev/null +++ b/core/src/main/java/com/zigythebird/playeranimcore/bindings/PlatformModel.java @@ -0,0 +1,4 @@ +package com.zigythebird.playeranimcore.bindings; + +public interface PlatformModel { +} diff --git a/core/src/main/java/com/zigythebird/playeranimcore/bones/CustomBone.java b/core/src/main/java/com/zigythebird/playeranimcore/bones/CustomBone.java new file mode 100644 index 00000000..aa892a09 --- /dev/null +++ b/core/src/main/java/com/zigythebird/playeranimcore/bones/CustomBone.java @@ -0,0 +1,26 @@ +package com.zigythebird.playeranimcore.bones; + +import com.zigythebird.playeranimcore.bindings.PlatformModel; +import com.zigythebird.playeranimcore.math.Vec3f; +import org.jetbrains.annotations.Nullable; + +public class CustomBone extends PlayerAnimBone { + private final Vec3f pivot; + + @Nullable + private final PlatformModel model; + + public CustomBone(String name, Vec3f pivot, @Nullable PlatformModel model) { + super(name); + this.pivot = pivot; + this.model = model; + } + + public Vec3f getPivot() { + return this.pivot; + } + + public @Nullable PlatformModel getModel() { + return this.model; + } +} diff --git a/core/src/main/java/com/zigythebird/playeranimcore/bones/PivotBone.java b/core/src/main/java/com/zigythebird/playeranimcore/bones/PivotBone.java deleted file mode 100644 index 567b6949..00000000 --- a/core/src/main/java/com/zigythebird/playeranimcore/bones/PivotBone.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.zigythebird.playeranimcore.bones; - -import com.zigythebird.playeranimcore.math.Vec3f; - -public class PivotBone extends PlayerAnimBone { - private final Vec3f pivot; - - public PivotBone(String name, Vec3f pivot) { - super(name); - this.pivot = pivot; - } - - public Vec3f getPivot() { - return this.pivot; - } -} diff --git a/core/src/main/java/com/zigythebird/playeranimcore/loading/AnimationLoader.java b/core/src/main/java/com/zigythebird/playeranimcore/loading/AnimationLoader.java index 9aaa8fbd..3d6bd96f 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/loading/AnimationLoader.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/loading/AnimationLoader.java @@ -27,6 +27,7 @@ import com.google.gson.*; import com.zigythebird.playeranimcore.PlayerAnimLib; import com.zigythebird.playeranimcore.animation.Animation; +import com.zigythebird.playeranimcore.animation.CustomAnimationBone; import com.zigythebird.playeranimcore.animation.ExtraAnimationData; import com.zigythebird.playeranimcore.animation.keyframe.BoneAnimation; import com.zigythebird.playeranimcore.animation.keyframe.Keyframe; @@ -34,7 +35,6 @@ import com.zigythebird.playeranimcore.easing.EasingType; import com.zigythebird.playeranimcore.enums.Axis; import com.zigythebird.playeranimcore.enums.TransformType; -import com.zigythebird.playeranimcore.math.Vec3f; import com.zigythebird.playeranimcore.molang.MolangLoader; import com.zigythebird.playeranimcore.util.JsonUtil; import it.unimi.dsi.fastutil.floats.FloatObjectPair; @@ -63,7 +63,7 @@ public Animation deserialize(JsonElement json, Type typeOfT, JsonDeserialization Animation.Keyframes keyframes = context.deserialize(animationObj, Animation.Keyframes.class); Map parents = UniversalAnimLoader.getParents(JsonUtil.getAsJsonObject(animationObj, "parents", new JsonObject())); - Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(animationObj, "model", new JsonObject())); + Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(animationObj, "model", new JsonObject()), context); // Extra data ExtraAnimationData extraData = new ExtraAnimationData(); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java b/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java index 7f5b8c8c..e6440099 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java @@ -3,11 +3,11 @@ import com.google.gson.*; import com.zigythebird.playeranimcore.PlayerAnimLib; import com.zigythebird.playeranimcore.animation.Animation; +import com.zigythebird.playeranimcore.animation.CustomAnimationBone; import com.zigythebird.playeranimcore.animation.ExtraAnimationData; import com.zigythebird.playeranimcore.animation.keyframe.event.data.CustomInstructionKeyframeData; import com.zigythebird.playeranimcore.animation.keyframe.event.data.ParticleKeyframeData; import com.zigythebird.playeranimcore.animation.keyframe.event.data.SoundKeyframeData; -import com.zigythebird.playeranimcore.math.Vec3f; import com.zigythebird.playeranimcore.util.JsonUtil; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.jetbrains.annotations.NotNull; @@ -41,7 +41,7 @@ public static Map loadAnimations(InputStream resource) throws Map animationMap = PlayerAnimLib.GSON.fromJson(json.get("animations"), PlayerAnimLib.ANIMATIONS_MAP_TYPE); if (json.has("parents") && json.has("model")) { Map parents = UniversalAnimLoader.getParents(JsonUtil.getAsJsonObject(json, "parents", new JsonObject())); - Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(json, "model", new JsonObject())); + Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(json, "model", new JsonObject()), PlayerAnimLib.GSON::fromJson); for (Animation animation : animationMap.values()) { if (animation.bones().isEmpty()) { animation.bones().putAll(bones); @@ -86,13 +86,10 @@ public static Map getParents(JsonObject parentsObj) { return parents; } - public static Map getModel(JsonObject modelObj) { - Map bones = new HashMap<>(modelObj.size()); + public static Map getModel(JsonObject modelObj, JsonDeserializationContext ctx) { + Map bones = new HashMap<>(modelObj.size()); for (Map.Entry entry : modelObj.entrySet()) { - JsonObject object = entry.getValue().getAsJsonObject(); - JsonArray pivot = object.get("pivot").getAsJsonArray(); - Vec3f bone = new Vec3f(pivot.get(0).getAsFloat(), pivot.get(1).getAsFloat(), pivot.get(2).getAsFloat()); - bones.put(entry.getKey(), bone); + bones.put(entry.getKey(), ctx.deserialize(entry.getValue(), CustomAnimationBone.class)); } return bones; } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinary.java b/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinary.java index 45544a35..4c8522d8 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinary.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinary.java @@ -1,6 +1,7 @@ package com.zigythebird.playeranimcore.network; import com.zigythebird.playeranimcore.animation.Animation; +import com.zigythebird.playeranimcore.animation.CustomAnimationBone; import com.zigythebird.playeranimcore.animation.ExtraAnimationData; import com.zigythebird.playeranimcore.animation.keyframe.BoneAnimation; import com.zigythebird.playeranimcore.animation.keyframe.Keyframe; @@ -12,7 +13,6 @@ import com.zigythebird.playeranimcore.enums.AnimationFormat; import com.zigythebird.playeranimcore.enums.TransformType; import com.zigythebird.playeranimcore.loading.PlayerAnimatorLoader; -import com.zigythebird.playeranimcore.math.Vec3f; import io.netty.buffer.ByteBuf; import team.unnamed.mocha.parser.ast.Expression; import team.unnamed.mocha.parser.ast.FloatExpression; @@ -81,7 +81,7 @@ public static void write(ByteBuf buf, int version, Animation animation) { } writeEventKeyframes(buf, animation.keyFrames()); - NetworkUtils.writeMap(buf, animation.bones(), ProtocolUtils::writeString, NetworkUtils::writeVec3f); + NetworkUtils.writeMap(buf, animation.bones(), ProtocolUtils::writeString, (byteBuf, bone) -> NetworkUtils.writeCustomBone(byteBuf, bone, version)); NetworkUtils.writeMap(buf, animation.parents(), ProtocolUtils::writeString, ProtocolUtils::writeString); } @@ -181,7 +181,7 @@ public static Animation read(ByteBuf buf, int version) { } Animation.Keyframes keyFrames = readEventKeyframes(buf); - Map pivotBones = NetworkUtils.readMap(buf, ProtocolUtils::readString, NetworkUtils::readVec3f); + Map pivotBones = NetworkUtils.readMap(buf, ProtocolUtils::readString, byteBuf -> NetworkUtils.readCustomBone(byteBuf, version)); Map parents = NetworkUtils.readMap(buf, ProtocolUtils::readString, ProtocolUtils::readString); return new Animation(data, length, loopType, boneAnimations, keyFrames, pivotBones, parents); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinaryV6.java b/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinaryV6.java index e7159fc6..45a5b526 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinaryV6.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinaryV6.java @@ -1,6 +1,7 @@ package com.zigythebird.playeranimcore.network; import com.zigythebird.playeranimcore.animation.Animation; +import com.zigythebird.playeranimcore.animation.CustomAnimationBone; import com.zigythebird.playeranimcore.animation.ExtraAnimationData; import com.zigythebird.playeranimcore.animation.keyframe.BoneAnimation; import com.zigythebird.playeranimcore.animation.keyframe.Keyframe; @@ -8,7 +9,6 @@ import com.zigythebird.playeranimcore.easing.EasingType; import com.zigythebird.playeranimcore.enums.AnimationFormat; import com.zigythebird.playeranimcore.loading.PlayerAnimatorLoader; -import com.zigythebird.playeranimcore.math.Vec3f; import io.netty.buffer.ByteBuf; import team.unnamed.mocha.parser.ast.Expression; import team.unnamed.mocha.parser.ast.FloatExpression; @@ -37,7 +37,7 @@ static void write(ByteBuf buf, int version, Animation animation) { } AnimationBinary.writeEventKeyframes(buf, animation.keyFrames()); - NetworkUtils.writeMap(buf, animation.bones(), ProtocolUtils::writeString, NetworkUtils::writeVec3f); + NetworkUtils.writeMap(buf, animation.bones(), ProtocolUtils::writeString, (byteBuf, bone) -> NetworkUtils.writeCustomBone(byteBuf, bone, version)); NetworkUtils.writeMap(buf, animation.parents(), ProtocolUtils::writeString, ProtocolUtils::writeString); } @@ -76,7 +76,7 @@ static Animation read(ByteBuf buf, int version) { buf1 -> readBoneAnimation(buf1, isPlayerAnimator, version)); Animation.Keyframes keyFrames = AnimationBinary.readEventKeyframes(buf); - Map pivotBones = NetworkUtils.readMap(buf, ProtocolUtils::readString, NetworkUtils::readVec3f); + Map pivotBones = NetworkUtils.readMap(buf, ProtocolUtils::readString, byteBuf -> NetworkUtils.readCustomBone(byteBuf, version)); Map parents = NetworkUtils.readMap(buf, ProtocolUtils::readString, ProtocolUtils::readString); return new Animation(data, length, loopType, boneAnimations, keyFrames, pivotBones, parents); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java b/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java index 33f8e970..d6e2c606 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java @@ -1,5 +1,6 @@ package com.zigythebird.playeranimcore.network; +import com.zigythebird.playeranimcore.animation.CustomAnimationBone; import com.zigythebird.playeranimcore.math.Vec3f; import io.netty.buffer.ByteBuf; import team.unnamed.mocha.util.network.VarIntUtils; @@ -30,6 +31,15 @@ public static void writeMap(ByteBuf buf, Map map, BiConsumer T compute(SharedOperationKey key) { + return key.compute(this); + } + }; + + TextureSlots slots = getTopTextureSlots(); + Material.Baked particle = resolveParticleMaterial(slots, baker); + QuadCollection geometry = bakeTopGeometry(slots, baker, BlockModelRotation.IDENTITY); + + boolean translucent = false; + for (BakedQuad quad : geometry.getAll()) { + if (quad.spriteInfo().layer().translucent()) { + translucent = true; + break; + } + } + + return new SimpleModelWrapper(geometry, getTopAmbientOcclusion(), particle, translucent); + } + + @Override + public @NonNull UnbakedModel wrapped() { + return this.model; + } + + @Override + public @Nullable ResolvedModel parent() { + return null; + } + + @Override + public @NonNull String debugName() { + return "playeranim:custom_bone"; + } +} diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java b/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java index b87abf00..e94acf3e 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java @@ -1,5 +1,6 @@ package com.zigythebird.playeranim.animation; +import com.google.gson.JsonObject; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; import com.zigythebird.playeranim.PlayerAnimLibMod; @@ -7,6 +8,8 @@ import com.zigythebird.playeranimcore.animation.AnimationController; import com.zigythebird.playeranimcore.animation.HumanoidAnimationController; import com.zigythebird.playeranimcore.animation.layered.modifier.AbstractFadeModifier; +import com.zigythebird.playeranimcore.bindings.PlatformModel; +import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import com.zigythebird.playeranimcore.math.Vec3f; import com.zigythebird.playeranimcore.molang.MolangLoader; import net.minecraft.resources.Identifier; @@ -53,7 +56,7 @@ public boolean triggerAnimation(Identifier newAnimation, float startAnimFrom) { triggerAnimation(PlayerAnimResources.getAnimation(newAnimation), startAnimFrom); return true; } - PlayerAnimLibMod.LOGGER.error("Could not find animation with the name:" + newAnimation); + PlayerAnimLibMod.LOGGER.error("Could not find animation with the name:{}", newAnimation); return false; } @@ -78,12 +81,22 @@ public boolean replaceAnimationWithFade(@NotNull AbstractFadeModifier fadeModifi */ public @Nullable PoseStack getBoneWorldPositionPoseStack(String name, float tickDelta, Vec3 cameraPos) { if (!this.activeBones.containsKey(name)) return null; + return getBoneWorldPositionPoseStack(this.activeBones.get(name), tickDelta, cameraPos); + } + + public @NotNull PoseStack getBoneWorldPositionPoseStack(PlayerAnimBone bone, float tickDelta, Vec3 cameraPos) { PoseStack poseStack = new PoseStack(); - Vec3f pivot = getBonePosition(name); + Vec3f pivot = getBonePosition(bone.getName()); Vec3 position = avatar.getPosition(tickDelta).subtract(cameraPos).add(pivot.x(), pivot.y(), pivot.z()); poseStack.translate(position.x(), position.y(), position.z()); poseStack.mulPose(Axis.YP.rotationDegrees(180 - Mth.lerp(tickDelta, avatar.yBodyRotO, avatar.yBodyRot))); - RenderUtil.translateMatrixToBone(poseStack, this.activeBones.get(name)); + RenderUtil.translateMatrixToBone(poseStack, bone); return poseStack; } + + @Override + protected PlatformModel loadCustomModel(JsonObject model) { + if (model == null || model.isEmpty()) return null; + return new MinecraftModel(model); + } } diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java index 950f5cae..cc629ca4 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java @@ -25,13 +25,29 @@ package com.zigythebird.playeranim.mixin; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.QuadInstance; import com.zigythebird.playeranim.accessors.IAvatarAnimationState; +import com.zigythebird.playeranim.animation.AvatarAnimManager; +import com.zigythebird.playeranim.animation.MinecraftModel; +import com.zigythebird.playeranim.animation.PlayerAnimationController; import com.zigythebird.playeranim.util.RenderUtil; +import com.zigythebird.playeranimcore.animation.AnimationController; +import com.zigythebird.playeranimcore.animation.layered.AnimationContainer; +import com.zigythebird.playeranimcore.animation.layered.IAnimation; +import com.zigythebird.playeranimcore.animation.layered.ModifierLayer; +import com.zigythebird.playeranimcore.bindings.PlatformModel; +import com.zigythebird.playeranimcore.bones.CustomBone; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.BlockModelPart; import net.minecraft.client.renderer.entity.LivingEntityRenderer; import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.renderer.state.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; +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; @@ -62,4 +78,69 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm } } } + + @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/SubmitNodeCollector;submitModel(Lnet/minecraft/client/model/Model;Ljava/lang/Object;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/rendertype/RenderType;IIILnet/minecraft/client/renderer/texture/TextureAtlasSprite;ILnet/minecraft/client/renderer/feature/ModelFeatureRenderer$CrumblingOverlay;)V" + ) + ) + public void pal$renderCustomModels(S state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState camera, CallbackInfo ci) { + if (!(state instanceof IAvatarAnimationState avatarRenderState)) return; + AvatarAnimManager animationPlayer = avatarRenderState.playerAnimLib$getAnimManager(); + if (animationPlayer == null || !animationPlayer.isActive()) return; + + int lightCoords = state.lightCoords; + + for (Pair layer : animationPlayer.getLayers()) { + AnimationController controller = pal$unwrapController(layer.right()); + if (controller == null || !controller.isActive()) continue; + + for (CustomBone bone : controller.getPivotBones().values()) { + PlatformModel platformModel = bone.getModel(); + if (!(platformModel instanceof MinecraftModel mcModel)) continue; + + BlockModelPart bakedPart = mcModel.getBakedModel(); + if (bakedPart == null) continue; + + boolean translucent = bakedPart instanceof net.minecraft.client.renderer.block.model.SimpleModelWrapper smw && smw.hasTranslucency(); + + poseStack.pushPose(); + controller.get3DTransform(bone); + RenderUtil.translateMatrixToBone(poseStack, bone); + + submitNodeCollector.submitCustomGeometry( + poseStack, + translucent ? Sheets.translucentBlockSheet() : Sheets.cutoutBlockSheet(), + (pose, buffer) -> { + QuadInstance instance = new QuadInstance(); + instance.setLightCoords(lightCoords); + instance.setOverlayCoords(OverlayTexture.NO_OVERLAY); + + for (BakedQuad quad : bakedPart.getQuads(null)) { + buffer.putBakedQuad(pose, quad, instance); + } + for (net.minecraft.core.Direction dir : net.minecraft.core.Direction.values()) { + for (BakedQuad quad : bakedPart.getQuads(dir)) { + buffer.putBakedQuad(pose, quad, instance); + } + } + } + ); + + poseStack.popPose(); + } + } + } + + private static AnimationController pal$unwrapController(IAnimation anim) { + while (true) { + if (anim instanceof AnimationController controller) return controller; + if (anim instanceof ModifierLayer ml) anim = ml.getAnimation(); + else if (anim instanceof AnimationContainer ac) anim = ac.getAnim(); + else return null; + if (anim == null) return null; + } + } } diff --git a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json new file mode 100644 index 00000000..d463062f --- /dev/null +++ b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json @@ -0,0 +1,40 @@ +{ + "format_version": "1.8.0", + "model": { + "cmm": { + "pivot": [ 0, 12, 0 ], + + "model": { + "ambientocclusion": false, + "textures": { + "particle": "block/lily_pad", + "texture": "block/lily_pad" + }, + "elements": [ + { "from": [ 0, 0.25, 0 ], + "to": [ 16, 0.25, 16 ], + "faces": { + "down": { "uv": [ 0, 16, 16, 0 ], "texture": "#texture", "tintindex": 0 }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "tintindex": 0 } + } + } + ] + } + } + }, + "parents": { + "head": "cmm" + }, + "animations": { + "cmm_test1": { + "loop": true, + "bones": { + "cmm": { + "rotation": { + "vector": [0, "q.anim_time * 20", 0] + } + } + } + } + } +} \ No newline at end of file diff --git a/minecraft/src/main/resources/player_animation_library.classtweaker b/minecraft/src/main/resources/player_animation_library.classtweaker index b82a5ebb..f92163dc 100644 --- a/minecraft/src/main/resources/player_animation_library.classtweaker +++ b/minecraft/src/main/resources/player_animation_library.classtweaker @@ -3,5 +3,6 @@ extendable class net/minecraft/client/model/geom/ModelPart accessible field net/minecraft/client/renderer/LevelRenderer levelRenderState Lnet/minecraft/client/renderer/state/LevelRenderState; accessible field net/minecraft/client/renderer/entity/layers/RenderLayer renderer Lnet/minecraft/client/renderer/entity/RenderLayerParent; accessible field net/minecraft/client/renderer/entity/LivingEntityRenderer layers Ljava/util/List; +accessible field net/minecraft/client/renderer/block/model/BlockModel GSON Lcom/google/gson/Gson; # inject-interface com/zigythebird/playeranim/accessors/IAnimatedAvatar net/minecraft/world/entity/Avatar # inject-interface com/zigythebird/playeranim/accessors/IAvatarAnimationState net/minecraft/client/renderer/entity/state/AvatarRenderState From d6cb68d027fe49cf937a9ccbd3e2d01d6e36baed Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Fri, 27 Feb 2026 01:53:27 +0700 Subject: [PATCH 03/13] fix one --- .../playeranimcore/loading/UniversalAnimLoader.java | 8 ++++++-- .../player_animations/cmm_test.json | 3 --- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java b/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java index e6440099..4331dcdf 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java @@ -39,13 +39,17 @@ public static Map loadAnimations(InputStream resource) throws public static Map<@NotNull String, Animation> loadAnimations(JsonObject json) { if (json.has("animations")) { Map animationMap = PlayerAnimLib.GSON.fromJson(json.get("animations"), PlayerAnimLib.ANIMATIONS_MAP_TYPE); - if (json.has("parents") && json.has("model")) { - Map parents = UniversalAnimLoader.getParents(JsonUtil.getAsJsonObject(json, "parents", new JsonObject())); + if (json.has("model")) { Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(json, "model", new JsonObject()), PlayerAnimLib.GSON::fromJson); for (Animation animation : animationMap.values()) { if (animation.bones().isEmpty()) { animation.bones().putAll(bones); } + } + } + if (json.has("parents")) { + Map parents = UniversalAnimLoader.getParents(JsonUtil.getAsJsonObject(json, "parents", new JsonObject())); + for (Animation animation : animationMap.values()) { if (animation.parents().isEmpty()) { animation.parents().putAll(parents); } diff --git a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json index d463062f..96929615 100644 --- a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json +++ b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json @@ -22,9 +22,6 @@ } } }, - "parents": { - "head": "cmm" - }, "animations": { "cmm_test1": { "loop": true, From c1f060a8bd833f047144956bc68d46eefbb0c646 Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Fri, 27 Feb 2026 03:42:35 +0700 Subject: [PATCH 04/13] fixes --- .../animation/AnimationController.java | 12 +- .../animation/layered/AnimationContainer.java | 8 + .../animation/layered/AnimationSnapshot.java | 6 + .../animation/layered/AnimationStack.java | 11 + .../animation/layered/IAnimation.java | 5 + .../animation/layered/ModifierLayer.java | 50 +++-- .../bindings/PlatformModel.java | 1 + .../playeranim/animation/MinecraftModel.java | 197 ++++++++++++------ .../animation/PlayerAnimationController.java | 2 +- .../mixin/LivingEntityRendererMixin.java | 76 +++---- .../player_animations/cmm_test.json | 3 +- .../player_animation_library.classtweaker | 1 + 12 files changed, 229 insertions(+), 143 deletions(-) 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 f0eb30f3..dd23f064 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java @@ -750,6 +750,14 @@ else if (pivotBones.containsKey(entry.getKey())) protected abstract @Nullable PlatformModel loadCustomModel(JsonObject model); + @Override + public void collectModels(Consumer consumer) { + for (CustomBone customBone : this.pivotBones.values()) { + if (customBone.getModel() == null) continue; + consumer.accept(customBone); + } + } + /** * Compute animation value for the given keyframes at the specified tick */ @@ -916,10 +924,6 @@ public void setupAnim(AnimationData state) { else process(state); } - public Map getPivotBones() { - return pivotBones; - } - public Vec3f getBonePosition(String name) { if (bonePositions.containsKey(name)) return bonePositions.get(name); if (pivotBones.containsKey(name)) return pivotBones.get(name).getPivot(); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationContainer.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationContainer.java index 5433348a..d5a235c8 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationContainer.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationContainer.java @@ -27,10 +27,13 @@ import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonConfiguration; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonMode; +import com.zigythebird.playeranimcore.bones.CustomBone; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.function.Consumer; + /** * A container to make swapping animation object easier * It will clone the behaviour of the held animation @@ -94,4 +97,9 @@ public String toString() { "anim=" + anim + '}'; } + + @Override + public void collectModels(Consumer consumer) { + if (this.anim != null) this.anim.collectModels(consumer); + } } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationSnapshot.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationSnapshot.java index afc51c9d..a81f715a 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationSnapshot.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationSnapshot.java @@ -1,10 +1,12 @@ package com.zigythebird.playeranimcore.animation.layered; +import com.zigythebird.playeranimcore.bones.CustomBone; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import com.zigythebird.playeranimcore.bones.ToggleablePlayerAnimBone; import org.jetbrains.annotations.NotNull; import java.util.Map; +import java.util.function.Consumer; public record AnimationSnapshot(Map snapshots) implements IAnimation { @Override @@ -25,4 +27,8 @@ public void get3DTransform(@NotNull PlayerAnimBone bone) { "snapshots=" + snapshots + '}'; } + + @Override + public void collectModels(Consumer consumer) { + } } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationStack.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationStack.java index 82b5793f..3045a3e7 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationStack.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationStack.java @@ -3,12 +3,14 @@ import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonConfiguration; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonMode; +import com.zigythebird.playeranimcore.bones.CustomBone; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import it.unimi.dsi.fastutil.Pair; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; /** * Player animation stack, can contain multiple active or passive layers, will always be evaluated from the lowest index. @@ -131,4 +133,13 @@ public String toString() { "layers=" + layers + '}'; } + + @Override + public void collectModels(Consumer consumer) { + for (Pair layer : this.layers) { + if (layer.right().isActive()) { + layer.right().collectModels(consumer); + } + } + } } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/IAnimation.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/IAnimation.java index 87aaff0b..6af8ac45 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/IAnimation.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/IAnimation.java @@ -27,9 +27,12 @@ import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonConfiguration; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonMode; +import com.zigythebird.playeranimcore.bones.CustomBone; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import org.jetbrains.annotations.NotNull; +import java.util.function.Consumer; + public interface IAnimation { FirstPersonConfiguration DEFAULT_FIRST_PERSON_CONFIG = new FirstPersonConfiguration(); @@ -88,4 +91,6 @@ default FirstPersonConfiguration getFirstPersonConfiguration() { default boolean canRemove() { return false; } + + void collectModels(Consumer consumer); } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/ModifierLayer.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/ModifierLayer.java index 407b9504..dc8f5814 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/ModifierLayer.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/ModifierLayer.java @@ -5,6 +5,7 @@ import com.zigythebird.playeranimcore.animation.layered.modifier.AbstractModifier; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonConfiguration; import com.zigythebird.playeranimcore.api.firstPerson.FirstPersonMode; +import com.zigythebird.playeranimcore.bones.CustomBone; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import com.zigythebird.playeranimcore.easing.EasingType; import org.jetbrains.annotations.NotNull; @@ -14,6 +15,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.function.Consumer; import java.util.function.Predicate; @@ -41,6 +43,16 @@ public ModifierLayer() { return animation; } + protected @Nullable IAnimation getTopAnimation() { + if (!this.modifiers.isEmpty()) { + return this.modifiers.getFirst(); + } else if (this.animation != null) { + return this.animation; + } else { + return null; + } + } + @Override public void tick(AnimationData state) { for (int i = 0; i < modifiers.size(); i++) { @@ -48,9 +60,8 @@ public void tick(AnimationData state) { removeModifier(i--); } } - if (modifiers.size() > 0) { - modifiers.get(0).tick(state); - } else if (animation != null) animation.tick(state); + IAnimation top = getTopAnimation(); + if (top != null) top.tick(state); } public void addModifier(@NotNull AbstractModifier modifier, int idx) { @@ -141,41 +152,34 @@ protected void linkModifiers() { @Override public boolean isActive() { - if (!modifiers.isEmpty()) { - return modifiers.get(0).isActive(); - } else if (animation != null) return animation.isActive(); + IAnimation top = getTopAnimation(); + if (top != null) return top.isActive(); return false; } @Override public void get3DTransform(@NotNull PlayerAnimBone bone) { - if (!modifiers.isEmpty()) { - modifiers.getFirst().get3DTransform(bone); - } else if (animation != null) { - animation.get3DTransform(bone); - } + IAnimation top = getTopAnimation(); + if (top != null) top.get3DTransform(bone); } @Override public void setupAnim(AnimationData state) { - if (!modifiers.isEmpty()) { - modifiers.get(0).setupAnim(state); - } else if (animation != null) animation.setupAnim(state); + IAnimation top = getTopAnimation(); + if (top != null) top.setupAnim(state); } @Override public @NotNull FirstPersonMode getFirstPersonMode() { - if (!modifiers.isEmpty()) { - return modifiers.get(0).getFirstPersonMode(); - } else if (animation != null) return animation.getFirstPersonMode(); + IAnimation top = getTopAnimation(); + if (top != null) return top.getFirstPersonMode(); return IAnimation.super.getFirstPersonMode(); } @Override public @NotNull FirstPersonConfiguration getFirstPersonConfiguration() { - if (!modifiers.isEmpty()) { - return modifiers.get(0).getFirstPersonConfiguration(); - } else if (animation != null) return animation.getFirstPersonConfiguration(); + IAnimation top = getTopAnimation(); + if (top != null) return top.getFirstPersonConfiguration(); return IAnimation.super.getFirstPersonConfiguration(); } @@ -186,4 +190,10 @@ public String toString() { ", animation=" + animation + '}'; } + + @Override + public void collectModels(Consumer consumer) { + IAnimation top = getTopAnimation(); + if (top != null) top.collectModels(consumer); + } } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/bindings/PlatformModel.java b/core/src/main/java/com/zigythebird/playeranimcore/bindings/PlatformModel.java index 83ffca04..85edf661 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/bindings/PlatformModel.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/bindings/PlatformModel.java @@ -1,4 +1,5 @@ package com.zigythebird.playeranimcore.bindings; public interface PlatformModel { + void invalidate(); } diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java b/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java index 48f75064..d195e164 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java @@ -1,6 +1,11 @@ package com.zigythebird.playeranim.animation; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.RenderSystem; +import com.zigythebird.playeranim.PlayerAnimLibMod; import com.zigythebird.playeranimcore.PlayerAnimLib; import com.zigythebird.playeranimcore.bindings.PlatformModel; import net.minecraft.client.Minecraft; @@ -10,108 +15,115 @@ import net.minecraft.client.renderer.block.model.Material; import net.minecraft.client.renderer.block.model.SimpleModelWrapper; import net.minecraft.client.renderer.block.model.TextureSlots; -import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.rendertype.RenderType; +import net.minecraft.client.renderer.rendertype.RenderTypes; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.client.renderer.texture.SpriteContents; import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.client.resources.metadata.animation.FrameSize; import net.minecraft.client.resources.model.BlockModelRotation; import net.minecraft.client.resources.model.MaterialBaker; import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.ModelDebugName; import net.minecraft.client.resources.model.QuadCollection; import net.minecraft.client.resources.model.ResolvedModel; -import net.minecraft.client.resources.model.SpriteId; import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.resources.Identifier; import org.jetbrains.annotations.Nullable; import org.joml.Vector3fc; import org.jspecify.annotations.NonNull; -public class MinecraftModel implements PlatformModel, ResolvedModel { - private final BlockModel model; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class MinecraftModel implements PlatformModel, ResolvedModel, MaterialBaker, ModelBaker, ModelBaker.Interner { + private static final AtomicInteger TEXTURE_COUNTER = new AtomicInteger(0); + + private final Map sprites = new HashMap<>(); + private final BlockModel model; + @Nullable + private final RenderType renderType; @Nullable private BlockModelPart bakedModel; - private boolean bakingAttempted; public MinecraftModel(JsonObject obj) { + obj.remove("display"); + obj.remove("gui_light"); + + Identifier primaryTextureId = null; + + if (obj.has("textures") && obj.get("textures").isJsonObject()) { + JsonObject textures = obj.getAsJsonObject("textures").deepCopy(); + obj.add("textures", textures); + + for (Map.Entry entry : textures.entrySet()) { + String value = entry.getValue().getAsString(); + if (value.startsWith("#")) continue; + + Identifier texId = PlayerAnimLibMod.id("dynamic/texture_" + TEXTURE_COUNTER.getAndIncrement()); + + try { + RenderSystem.assertOnRenderThread(); + + NativeImage image = NativeImage.read(Base64.getDecoder().decode(value)); + + int w = image.getWidth(); + int h = image.getHeight(); + + DynamicTexture dynTex = new DynamicTexture(texId::toString, image); + Minecraft.getInstance().getTextureManager().register(texId, dynTex); + + SpriteContents contents = new SpriteContents(texId, new FrameSize(w, h), image); + this.sprites.put(texId, new TextureAtlasSprite(texId, contents, w, h, 0, 0, 0)); + } catch (Exception e) { + PlayerAnimLib.LOGGER.error("Failed to load texture for bone model", e); + } + + entry.setValue(new JsonPrimitive(texId.toString())); + if (primaryTextureId == null) primaryTextureId = texId; + } + } + this.model = BlockModel.GSON.fromJson(obj, BlockModel.class); + this.renderType = primaryTextureId != null ? RenderTypes.entityCutoutCull(primaryTextureId) : null; } @Nullable public BlockModelPart getBakedModel() { - if (!bakingAttempted) { - bakingAttempted = true; + if (this.bakedModel == null && !this.sprites.isEmpty()) { try { - bakedModel = bake(); + this.bakedModel = bake(); } catch (Exception e) { PlayerAnimLib.LOGGER.error("Failed to bake custom bone model", e); } } - return bakedModel; + return this.bakedModel; + } + + @Nullable + public RenderType getRenderType() { + return this.renderType; } + @Override public void invalidate() { - bakingAttempted = false; - bakedModel = null; + TextureManager textureManager = Minecraft.getInstance().getTextureManager(); + for (Identifier texId : this.sprites.keySet()) { + textureManager.release(texId); + } + this.sprites.clear(); + this.bakedModel = null; } private @NonNull BlockModelPart bake() { - TextureAtlasSprite missingSprite = Minecraft.getInstance().getAtlasManager() - .get(new SpriteId(TextureAtlas.LOCATION_BLOCKS, Identifier.withDefaultNamespace("missingno"))); - - MaterialBaker materialBaker = new MaterialBaker() { - @Override - public Material.@NonNull Baked get(Material material, @NonNull ModelDebugName name) { - SpriteId id = new SpriteId(TextureAtlas.LOCATION_BLOCKS, material.sprite()); - TextureAtlasSprite sprite = Minecraft.getInstance().getAtlasManager().get(id); - return new Material.Baked(sprite, material.forceTranslucent()); - } - - @Override - public Material.@NonNull Baked reportMissingReference(@NonNull String reference, @NonNull ModelDebugName name) { - return new Material.Baked(missingSprite, false); - } - }; - - ModelBaker baker = new ModelBaker() { - @Override - public @NonNull ResolvedModel getModel(@NonNull Identifier location) { - return MinecraftModel.this; - } - - @Override - public @NonNull BlockModelPart missingBlockModelPart() { - throw new UnsupportedOperationException(); - } - - @Override - public @NonNull MaterialBaker materials() { - return materialBaker; - } - - @Override - public @NonNull Interner interner() { - return new Interner() { - @Override - public @NonNull Vector3fc vector(@NonNull Vector3fc vector) { - return vector; - } - - @Override - public BakedQuad.@NonNull SpriteInfo spriteInfo(BakedQuad.@NonNull SpriteInfo sprite) { - return sprite; - } - }; - } - - @Override - public T compute(SharedOperationKey key) { - return key.compute(this); - } - }; - TextureSlots slots = getTopTextureSlots(); - Material.Baked particle = resolveParticleMaterial(slots, baker); - QuadCollection geometry = bakeTopGeometry(slots, baker, BlockModelRotation.IDENTITY); + + Material.Baked particle = resolveParticleMaterial(slots, this); + QuadCollection geometry = bakeTopGeometry(slots, this, BlockModelRotation.IDENTITY); boolean translucent = false; for (BakedQuad quad : geometry.getAll()) { @@ -138,4 +150,55 @@ public T compute(SharedOperationKey key) { public @NonNull String debugName() { return "playeranim:custom_bone"; } + + @Override + public Material.@NonNull Baked get(Material material, @NonNull ModelDebugName name) { + TextureAtlasSprite sprite = this.sprites.get(material.sprite()); + if (sprite == null && !this.sprites.isEmpty()) { + PlayerAnimLib.LOGGER.warn("Unknown texture: {} in {}", material.sprite(), name.debugName()); + sprite = this.sprites.values().iterator().next(); + } + return new Material.Baked(sprite, material.forceTranslucent()); + } + + @Override + public Material.@NonNull Baked reportMissingReference(@NonNull String reference, @NonNull ModelDebugName name) { + TextureAtlasSprite sprite = this.sprites.isEmpty() ? null : this.sprites.values().iterator().next(); + return new Material.Baked(sprite, false); + } + + @Override + public @NonNull ResolvedModel getModel(@NonNull Identifier location) { + return this; + } + + @Override + public @NonNull BlockModelPart missingBlockModelPart() { + throw new UnsupportedOperationException(); + } + + @Override + public @NonNull MaterialBaker materials() { + return this; + } + + @Override + public @NonNull Interner interner() { + return this; + } + + @Override + public @NonNull Vector3fc vector(@NonNull Vector3fc vector) { + return vector; + } + + @Override + public BakedQuad.@NonNull SpriteInfo spriteInfo(BakedQuad.@NonNull SpriteInfo sprite) { + return sprite; + } + + @Override + public T compute(SharedOperationKey key) { + return key.compute(this); + } } diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java b/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java index e94acf3e..f6237ba0 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java @@ -97,6 +97,6 @@ public boolean replaceAnimationWithFade(@NotNull AbstractFadeModifier fadeModifi @Override protected PlatformModel loadCustomModel(JsonObject model) { if (model == null || model.isEmpty()) return null; - return new MinecraftModel(model); + return new MinecraftModel(model.deepCopy()); } } diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java index cc629ca4..0bde1c11 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java @@ -29,25 +29,18 @@ import com.zigythebird.playeranim.accessors.IAvatarAnimationState; import com.zigythebird.playeranim.animation.AvatarAnimManager; import com.zigythebird.playeranim.animation.MinecraftModel; -import com.zigythebird.playeranim.animation.PlayerAnimationController; import com.zigythebird.playeranim.util.RenderUtil; -import com.zigythebird.playeranimcore.animation.AnimationController; -import com.zigythebird.playeranimcore.animation.layered.AnimationContainer; -import com.zigythebird.playeranimcore.animation.layered.IAnimation; -import com.zigythebird.playeranimcore.animation.layered.ModifierLayer; import com.zigythebird.playeranimcore.bindings.PlatformModel; -import com.zigythebird.playeranimcore.bones.CustomBone; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; -import it.unimi.dsi.fastutil.Pair; -import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BlockModelPart; import net.minecraft.client.renderer.entity.LivingEntityRenderer; import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; +import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.client.renderer.state.CameraRenderState; import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.world.phys.Vec3; +import net.minecraft.core.Direction; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -88,59 +81,44 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm ) public void pal$renderCustomModels(S state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState camera, CallbackInfo ci) { if (!(state instanceof IAvatarAnimationState avatarRenderState)) return; + AvatarAnimManager animationPlayer = avatarRenderState.playerAnimLib$getAnimManager(); if (animationPlayer == null || !animationPlayer.isActive()) return; int lightCoords = state.lightCoords; - for (Pair layer : animationPlayer.getLayers()) { - AnimationController controller = pal$unwrapController(layer.right()); - if (controller == null || !controller.isActive()) continue; - - for (CustomBone bone : controller.getPivotBones().values()) { - PlatformModel platformModel = bone.getModel(); - if (!(platformModel instanceof MinecraftModel mcModel)) continue; + animationPlayer.collectModels(bone -> { + PlatformModel platformModel = bone.getModel(); + if (!(platformModel instanceof MinecraftModel mcModel)) return; - BlockModelPart bakedPart = mcModel.getBakedModel(); - if (bakedPart == null) continue; + BlockModelPart bakedPart = mcModel.getBakedModel(); + RenderType modelRenderType = mcModel.getRenderType(); + if (bakedPart == null || modelRenderType == null) return; - boolean translucent = bakedPart instanceof net.minecraft.client.renderer.block.model.SimpleModelWrapper smw && smw.hasTranslucency(); + poseStack.pushPose(); + animationPlayer.get3DTransform(bone); + RenderUtil.translateMatrixToBone(poseStack, bone); - poseStack.pushPose(); - controller.get3DTransform(bone); - RenderUtil.translateMatrixToBone(poseStack, bone); + submitNodeCollector.submitCustomGeometry( + poseStack, modelRenderType, - submitNodeCollector.submitCustomGeometry( - poseStack, - translucent ? Sheets.translucentBlockSheet() : Sheets.cutoutBlockSheet(), - (pose, buffer) -> { - QuadInstance instance = new QuadInstance(); - instance.setLightCoords(lightCoords); - instance.setOverlayCoords(OverlayTexture.NO_OVERLAY); + (pose, buffer) -> { + QuadInstance instance = new QuadInstance(); + instance.setLightCoords(lightCoords); + instance.setOverlayCoords(OverlayTexture.NO_OVERLAY); - for (BakedQuad quad : bakedPart.getQuads(null)) { + for (BakedQuad quad : bakedPart.getQuads(null)) { + buffer.putBakedQuad(pose, quad, instance); + } + for (Direction dir : Direction.values()) { + for (BakedQuad quad : bakedPart.getQuads(dir)) { buffer.putBakedQuad(pose, quad, instance); } - for (net.minecraft.core.Direction dir : net.minecraft.core.Direction.values()) { - for (BakedQuad quad : bakedPart.getQuads(dir)) { - buffer.putBakedQuad(pose, quad, instance); - } - } } - ); + } + ); - poseStack.popPose(); - } - } - } - - private static AnimationController pal$unwrapController(IAnimation anim) { - while (true) { - if (anim instanceof AnimationController controller) return controller; - if (anim instanceof ModifierLayer ml) anim = ml.getAnimation(); - else if (anim instanceof AnimationContainer ac) anim = ac.getAnim(); - else return null; - if (anim == null) return null; - } + poseStack.popPose(); + }); } } diff --git a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json index 96929615..ca9c0f5b 100644 --- a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json +++ b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json @@ -7,8 +7,7 @@ "model": { "ambientocclusion": false, "textures": { - "particle": "block/lily_pad", - "texture": "block/lily_pad" + "texture": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAfUlEQVR4Xo1QiRHAIAhzUAZgC7foAg5KjSkq1OsVr1qS8Jby18TE1PivBi/R1a4JwmtRIp1uHUYOHeIlnxEOQoyz5ZBB40MOxNNPAsL6vKEIBIxBHx5/ECzqIHBwFQg9+BT7XeOguUTYw56DL5p9LZvrxpggE00DzJOZD7sBRaHjpRD91mQAAAAASUVORK5CYII=" }, "elements": [ { "from": [ 0, 0.25, 0 ], diff --git a/minecraft/src/main/resources/player_animation_library.classtweaker b/minecraft/src/main/resources/player_animation_library.classtweaker index f92163dc..fe1b5811 100644 --- a/minecraft/src/main/resources/player_animation_library.classtweaker +++ b/minecraft/src/main/resources/player_animation_library.classtweaker @@ -4,5 +4,6 @@ accessible field net/minecraft/client/renderer/LevelRenderer levelRenderState Ln accessible field net/minecraft/client/renderer/entity/layers/RenderLayer renderer Lnet/minecraft/client/renderer/entity/RenderLayerParent; accessible field net/minecraft/client/renderer/entity/LivingEntityRenderer layers Ljava/util/List; accessible field net/minecraft/client/renderer/block/model/BlockModel GSON Lcom/google/gson/Gson; +accessible method net/minecraft/client/renderer/texture/TextureAtlasSprite (Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/renderer/texture/SpriteContents;IIIII)V # inject-interface com/zigythebird/playeranim/accessors/IAnimatedAvatar net/minecraft/world/entity/Avatar # inject-interface com/zigythebird/playeranim/accessors/IAvatarAnimationState net/minecraft/client/renderer/entity/state/AvatarRenderState From a903eeb3508ac6c5bd3864d30a8be0dd9769839f Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Fri, 27 Feb 2026 03:49:49 +0700 Subject: [PATCH 05/13] geometry --- .../playeranimcore/animation/AnimationController.java | 2 +- .../playeranimcore/animation/CustomAnimationBone.java | 4 ++-- .../player_animation_library/player_animations/cmm_test.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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 dd23f064..b4a14475 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java @@ -742,7 +742,7 @@ else if (pivotBones.containsKey(entry.getKey())) this.pivotBones.clear(); for (Map.Entry entry : currentAnimation.animation().bones().entrySet()) { - this.pivotBones.put(entry.getKey(), new CustomBone(entry.getKey(), entry.getValue().pivot(), loadCustomModel(entry.getValue().model()))); + this.pivotBones.put(entry.getKey(), new CustomBone(entry.getKey(), entry.getValue().pivot(), loadCustomModel(entry.getValue().geometry()))); } this.postAnimationSetupConsumer.accept((name) -> bones.getOrDefault(name, null)); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java index feb30406..b4defb04 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java @@ -5,12 +5,12 @@ import java.lang.reflect.Type; -public record CustomAnimationBone(Vec3f pivot, JsonObject model) { +public record CustomAnimationBone(Vec3f pivot, JsonObject geometry) { public static class Deserializer implements JsonDeserializer { @Override public CustomAnimationBone deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) throws JsonParseException { JsonObject obj = json.getAsJsonObject(); - return new CustomAnimationBone(ctx.deserialize(obj.get("pivot"), Vec3f.class), obj.getAsJsonObject("model")); + return new CustomAnimationBone(ctx.deserialize(obj.get("pivot"), Vec3f.class), obj.getAsJsonObject("geometry")); } } } diff --git a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json index ca9c0f5b..ac070199 100644 --- a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json +++ b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json @@ -4,7 +4,7 @@ "cmm": { "pivot": [ 0, 12, 0 ], - "model": { + "geometry": { "ambientocclusion": false, "textures": { "texture": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAfUlEQVR4Xo1QiRHAIAhzUAZgC7foAg5KjSkq1OsVr1qS8Jby18TE1PivBi/R1a4JwmtRIp1uHUYOHeIlnxEOQoyz5ZBB40MOxNNPAsL6vKEIBIxBHx5/ECzqIHBwFQg9+BT7XeOguUTYw56DL5p9LZvrxpggE00DzJOZD7sBRaHjpRD91mQAAAAASUVORK5CYII=" From d780a93e49d9696fc49863b9fa03de0f32a0fccc Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Fri, 27 Feb 2026 04:42:33 +0700 Subject: [PATCH 06/13] hmmm --- .../animation/AnimationController.java | 9 +- .../animation/CustomAnimationBone.java | 9 +- .../playeranimcore/network/NetworkUtils.java | 2 +- .../playeranim/animation/MinecraftModel.java | 157 +++++------------- .../animation/PlayerAnimationController.java | 14 +- .../mixin/LivingEntityRendererMixin.java | 9 +- .../player_animations/cmm_test.json | 24 ++- 7 files changed, 77 insertions(+), 147 deletions(-) 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 b4a14475..3c3306d7 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java @@ -24,7 +24,7 @@ package com.zigythebird.playeranimcore.animation; -import com.google.gson.JsonObject; +import com.google.gson.JsonArray; import com.zigythebird.playeranimcore.PlayerAnimLib; import com.zigythebird.playeranimcore.animation.keyframe.*; import com.zigythebird.playeranimcore.animation.keyframe.event.CustomKeyFrameEvents; @@ -742,13 +742,16 @@ else if (pivotBones.containsKey(entry.getKey())) this.pivotBones.clear(); for (Map.Entry entry : currentAnimation.animation().bones().entrySet()) { - this.pivotBones.put(entry.getKey(), new CustomBone(entry.getKey(), entry.getValue().pivot(), loadCustomModel(entry.getValue().geometry()))); + this.pivotBones.put(entry.getKey(), new CustomBone(entry.getKey(), entry.getValue().pivot(), loadCustomModel( + entry.getValue().texture(), + entry.getValue().elements() + ))); } this.postAnimationSetupConsumer.accept((name) -> bones.getOrDefault(name, null)); } - protected abstract @Nullable PlatformModel loadCustomModel(JsonObject model); + protected abstract @Nullable PlatformModel loadCustomModel(@Nullable String texture, @Nullable JsonArray elements); @Override public void collectModels(Consumer consumer) { diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java index b4defb04..4e16889b 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java @@ -2,15 +2,20 @@ import com.google.gson.*; import com.zigythebird.playeranimcore.math.Vec3f; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Type; -public record CustomAnimationBone(Vec3f pivot, JsonObject geometry) { +public record CustomAnimationBone(Vec3f pivot, @Nullable String texture, @Nullable JsonArray elements) { public static class Deserializer implements JsonDeserializer { @Override public CustomAnimationBone deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) throws JsonParseException { JsonObject obj = json.getAsJsonObject(); - return new CustomAnimationBone(ctx.deserialize(obj.get("pivot"), Vec3f.class), obj.getAsJsonObject("geometry")); + return new CustomAnimationBone( + ctx.deserialize(obj.get("pivot"), Vec3f.class), + obj.has("texture") ? obj.get("texture").getAsString() : null, + obj.getAsJsonArray("elements") + ); } } } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java b/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java index d6e2c606..d61dbda6 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java @@ -33,7 +33,7 @@ public static void writeMap(ByteBuf buf, Map map, BiConsumer sprites = new HashMap<>(); - - private final BlockModel model; - @Nullable + private final TextureAtlasSprite sprite; private final RenderType renderType; - @Nullable - private BlockModelPart bakedModel; - - public MinecraftModel(JsonObject obj) { - obj.remove("display"); - obj.remove("gui_light"); - - Identifier primaryTextureId = null; - - if (obj.has("textures") && obj.get("textures").isJsonObject()) { - JsonObject textures = obj.getAsJsonObject("textures").deepCopy(); - obj.add("textures", textures); - - for (Map.Entry entry : textures.entrySet()) { - String value = entry.getValue().getAsString(); - if (value.startsWith("#")) continue; - - Identifier texId = PlayerAnimLibMod.id("dynamic/texture_" + TEXTURE_COUNTER.getAndIncrement()); - - try { - RenderSystem.assertOnRenderThread(); - - NativeImage image = NativeImage.read(Base64.getDecoder().decode(value)); - - int w = image.getWidth(); - int h = image.getHeight(); + private final QuadCollection geometry; - DynamicTexture dynTex = new DynamicTexture(texId::toString, image); - Minecraft.getInstance().getTextureManager().register(texId, dynTex); + public MinecraftModel(@Nullable String texture, @NotNull JsonArray elements) throws IOException { + this.sprite = loadAtlasSprite(texture); + this.renderType = RenderTypes.entityCutoutCull(this.sprite.atlasLocation()); - SpriteContents contents = new SpriteContents(texId, new FrameSize(w, h), image); - this.sprites.put(texId, new TextureAtlasSprite(texId, contents, w, h, 0, 0, 0)); - } catch (Exception e) { - PlayerAnimLib.LOGGER.error("Failed to load texture for bone model", e); - } - - entry.setValue(new JsonPrimitive(texId.toString())); - if (primaryTextureId == null) primaryTextureId = texId; - } + List blockElements = new ArrayList<>(); + for (JsonElement element : elements) { + blockElements.add(BlockModel.GSON.fromJson(element, BlockElement.class)); } - - this.model = BlockModel.GSON.fromJson(obj, BlockModel.class); - this.renderType = primaryTextureId != null ? RenderTypes.entityCutoutCull(primaryTextureId) : null; + this.geometry = SimpleUnbakedGeometry.bake(blockElements, TextureSlots.EMPTY, this, BlockModelRotation.IDENTITY, this); } - @Nullable - public BlockModelPart getBakedModel() { - if (this.bakedModel == null && !this.sprites.isEmpty()) { - try { - this.bakedModel = bake(); - } catch (Exception e) { - PlayerAnimLib.LOGGER.error("Failed to bake custom bone model", e); - } - } - return this.bakedModel; + @NotNull + public QuadCollection getGeometry() { + return this.geometry; } - @Nullable + @NotNull public RenderType getRenderType() { return this.renderType; } @Override public void invalidate() { - TextureManager textureManager = Minecraft.getInstance().getTextureManager(); - for (Identifier texId : this.sprites.keySet()) { - textureManager.release(texId); - } - this.sprites.clear(); - this.bakedModel = null; - } - - private @NonNull BlockModelPart bake() { - TextureSlots slots = getTopTextureSlots(); - - Material.Baked particle = resolveParticleMaterial(slots, this); - QuadCollection geometry = bakeTopGeometry(slots, this, BlockModelRotation.IDENTITY); - - boolean translucent = false; - for (BakedQuad quad : geometry.getAll()) { - if (quad.spriteInfo().layer().translucent()) { - translucent = true; - break; - } - } - - return new SimpleModelWrapper(geometry, getTopAmbientOcclusion(), particle, translucent); - } - - @Override - public @NonNull UnbakedModel wrapped() { - return this.model; - } - - @Override - public @Nullable ResolvedModel parent() { - return null; + if (this.sprite != null) this.sprite.close(); } @Override @@ -153,23 +67,17 @@ public void invalidate() { @Override public Material.@NonNull Baked get(Material material, @NonNull ModelDebugName name) { - TextureAtlasSprite sprite = this.sprites.get(material.sprite()); - if (sprite == null && !this.sprites.isEmpty()) { - PlayerAnimLib.LOGGER.warn("Unknown texture: {} in {}", material.sprite(), name.debugName()); - sprite = this.sprites.values().iterator().next(); - } - return new Material.Baked(sprite, material.forceTranslucent()); + return new Material.Baked(this.sprite, material.forceTranslucent()); } @Override public Material.@NonNull Baked reportMissingReference(@NonNull String reference, @NonNull ModelDebugName name) { - TextureAtlasSprite sprite = this.sprites.isEmpty() ? null : this.sprites.values().iterator().next(); - return new Material.Baked(sprite, false); + return new Material.Baked(this.sprite, false); } @Override public @NonNull ResolvedModel getModel(@NonNull Identifier location) { - return this; + throw new UnsupportedOperationException(); } @Override @@ -201,4 +109,19 @@ public void invalidate() { public T compute(SharedOperationKey key) { return key.compute(this); } + + private static TextureAtlasSprite loadAtlasSprite(String texture) throws IOException { + Identifier texId = PlayerAnimLibMod.id("dynamic/texture_" + TEXTURE_COUNTER.getAndIncrement()); + + RenderSystem.assertOnRenderThread(); + NativeImage image = NativeImage.read(Base64.getDecoder().decode(texture)); + + DynamicTexture dynTex = new DynamicTexture(texId::toString, image); + Minecraft.getInstance().getTextureManager().register(texId, dynTex); + + int w = image.getWidth(); + int h = image.getHeight(); + SpriteContents contents = new SpriteContents(texId, new FrameSize(w, h), image); + return new TextureAtlasSprite(texId, contents, w, h, 0, 0, 0); + } } diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java b/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java index f6237ba0..6d08f0ba 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java @@ -1,10 +1,11 @@ package com.zigythebird.playeranim.animation; -import com.google.gson.JsonObject; +import com.google.gson.JsonArray; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; import com.zigythebird.playeranim.PlayerAnimLibMod; import com.zigythebird.playeranim.util.RenderUtil; +import com.zigythebird.playeranimcore.PlayerAnimLib; import com.zigythebird.playeranimcore.animation.AnimationController; import com.zigythebird.playeranimcore.animation.HumanoidAnimationController; import com.zigythebird.playeranimcore.animation.layered.modifier.AbstractFadeModifier; @@ -95,8 +96,13 @@ public boolean replaceAnimationWithFade(@NotNull AbstractFadeModifier fadeModifi } @Override - protected PlatformModel loadCustomModel(JsonObject model) { - if (model == null || model.isEmpty()) return null; - return new MinecraftModel(model.deepCopy()); + protected @Nullable PlatformModel loadCustomModel(@Nullable String texture, @Nullable JsonArray elements) { + if (elements == null || elements.isEmpty()) return null; + try { + return new MinecraftModel(texture, elements); + } catch (Exception e) { + PlayerAnimLib.LOGGER.error("Failed to load custom model!", e); + return null; + } } } diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java index 0bde1c11..2ce8b1ab 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java @@ -34,12 +34,11 @@ import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockModelPart; import net.minecraft.client.renderer.entity.LivingEntityRenderer; import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; -import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.client.renderer.state.CameraRenderState; import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.resources.model.QuadCollection; import net.minecraft.core.Direction; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -91,16 +90,14 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm PlatformModel platformModel = bone.getModel(); if (!(platformModel instanceof MinecraftModel mcModel)) return; - BlockModelPart bakedPart = mcModel.getBakedModel(); - RenderType modelRenderType = mcModel.getRenderType(); - if (bakedPart == null || modelRenderType == null) return; + QuadCollection bakedPart = mcModel.getGeometry(); poseStack.pushPose(); animationPlayer.get3DTransform(bone); RenderUtil.translateMatrixToBone(poseStack, bone); submitNodeCollector.submitCustomGeometry( - poseStack, modelRenderType, + poseStack, mcModel.getRenderType(), (pose, buffer) -> { QuadInstance instance = new QuadInstance(); diff --git a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json index ac070199..9abb53c1 100644 --- a/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json +++ b/minecraft/src/main/resources/assets/player_animation_library/player_animations/cmm_test.json @@ -4,21 +4,17 @@ "cmm": { "pivot": [ 0, 12, 0 ], - "geometry": { - "ambientocclusion": false, - "textures": { - "texture": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAfUlEQVR4Xo1QiRHAIAhzUAZgC7foAg5KjSkq1OsVr1qS8Jby18TE1PivBi/R1a4JwmtRIp1uHUYOHeIlnxEOQoyz5ZBB40MOxNNPAsL6vKEIBIxBHx5/ECzqIHBwFQg9+BT7XeOguUTYw56DL5p9LZvrxpggE00DzJOZD7sBRaHjpRD91mQAAAAASUVORK5CYII=" - }, - "elements": [ - { "from": [ 0, 0.25, 0 ], - "to": [ 16, 0.25, 16 ], - "faces": { - "down": { "uv": [ 0, 16, 16, 0 ], "texture": "#texture", "tintindex": 0 }, - "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "tintindex": 0 } - } + "texture": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAfUlEQVR4Xo1QiRHAIAhzUAZgC7foAg5KjSkq1OsVr1qS8Jby18TE1PivBi/R1a4JwmtRIp1uHUYOHeIlnxEOQoyz5ZBB40MOxNNPAsL6vKEIBIxBHx5/ECzqIHBwFQg9+BT7XeOguUTYw56DL5p9LZvrxpggE00DzJOZD7sBRaHjpRD91mQAAAAASUVORK5CYII=", + "elements": [ + { + "from": [ 0, 0.25, 0 ], + "to": [ 16, 0.25, 16 ], + "faces": { + "down": { "uv": [ 0, 16, 16, 0 ], "texture": "#texture", "tintindex": 0 }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "tintindex": 0 } } - ] - } + } + ] } }, "animations": { From cdb05113dc848c835ea35f9878dfcf88dd25faff Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Fri, 27 Feb 2026 23:47:02 +0700 Subject: [PATCH 07/13] CustomModelBone --- .../com/zigythebird/playeranimcore/PlayerAnimLib.java | 4 ++-- .../playeranimcore/animation/Animation.java | 2 +- .../playeranimcore/animation/AnimationController.java | 2 +- .../{CustomAnimationBone.java => CustomModelBone.java} | 8 ++++---- .../playeranimcore/loading/AnimationLoader.java | 4 ++-- .../playeranimcore/loading/UniversalAnimLoader.java | 10 +++++----- .../playeranimcore/network/AnimationBinary.java | 4 ++-- .../playeranimcore/network/AnimationBinaryV6.java | 4 ++-- .../playeranimcore/network/NetworkUtils.java | 8 ++++---- 9 files changed, 23 insertions(+), 23 deletions(-) rename core/src/main/java/com/zigythebird/playeranimcore/animation/{CustomAnimationBone.java => CustomModelBone.java} (64%) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java b/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java index d197a6fe..64513cef 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/PlayerAnimLib.java @@ -4,7 +4,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.zigythebird.playeranimcore.animation.Animation; -import com.zigythebird.playeranimcore.animation.CustomAnimationBone; +import com.zigythebird.playeranimcore.animation.CustomModelBone; import com.zigythebird.playeranimcore.loading.AnimationLoader; import com.zigythebird.playeranimcore.loading.KeyFrameLoader; import com.zigythebird.playeranimcore.loading.UniversalAnimLoader; @@ -21,7 +21,7 @@ public class PlayerAnimLib { public static final Type ANIMATIONS_MAP_TYPE = new TypeToken>() {}.getType(); public static final Gson GSON = new GsonBuilder() - .registerTypeAdapter(CustomAnimationBone.class, new CustomAnimationBone.Deserializer()) + .registerTypeAdapter(CustomModelBone.class, new CustomModelBone.Deserializer()) .registerTypeAdapter(Vec3f.class, new Vec3f.Deserializer()) .registerTypeAdapter(Animation.Keyframes.class, new KeyFrameLoader()) .registerTypeAdapter(Animation.class, new AnimationLoader()) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/Animation.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/Animation.java index 5944cfaa..820e039f 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/Animation.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/Animation.java @@ -43,7 +43,7 @@ *

* Modifications or extensions of a compiled Animation are not supported, and therefore an instance of Animation is considered final and immutable */ -public record Animation(ExtraAnimationData data, float length, LoopType loopType, Map boneAnimations, Keyframes keyFrames, Map bones, Map parents) implements Supplier { +public record Animation(ExtraAnimationData data, float length, LoopType loopType, Map boneAnimations, Keyframes keyFrames, Map bones, Map parents) implements Supplier { public record Keyframes(SoundKeyframeData[] sounds, ParticleKeyframeData[] particles, CustomInstructionKeyframeData[] customInstructions) { @Override public int hashCode() { 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 3c3306d7..9b2f0fee 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java @@ -741,7 +741,7 @@ else if (pivotBones.containsKey(entry.getKey())) } this.pivotBones.clear(); - for (Map.Entry entry : currentAnimation.animation().bones().entrySet()) { + for (Map.Entry entry : currentAnimation.animation().bones().entrySet()) { this.pivotBones.put(entry.getKey(), new CustomBone(entry.getKey(), entry.getValue().pivot(), loadCustomModel( entry.getValue().texture(), entry.getValue().elements() diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomModelBone.java similarity index 64% rename from core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java rename to core/src/main/java/com/zigythebird/playeranimcore/animation/CustomModelBone.java index 4e16889b..cd38045d 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomAnimationBone.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomModelBone.java @@ -6,12 +6,12 @@ import java.lang.reflect.Type; -public record CustomAnimationBone(Vec3f pivot, @Nullable String texture, @Nullable JsonArray elements) { - public static class Deserializer implements JsonDeserializer { +public record CustomModelBone(Vec3f pivot, @Nullable String texture, @Nullable JsonArray elements) { + public static class Deserializer implements JsonDeserializer { @Override - public CustomAnimationBone deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) throws JsonParseException { + public CustomModelBone deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) throws JsonParseException { JsonObject obj = json.getAsJsonObject(); - return new CustomAnimationBone( + return new CustomModelBone( ctx.deserialize(obj.get("pivot"), Vec3f.class), obj.has("texture") ? obj.get("texture").getAsString() : null, obj.getAsJsonArray("elements") diff --git a/core/src/main/java/com/zigythebird/playeranimcore/loading/AnimationLoader.java b/core/src/main/java/com/zigythebird/playeranimcore/loading/AnimationLoader.java index 3d6bd96f..09480da1 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/loading/AnimationLoader.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/loading/AnimationLoader.java @@ -27,7 +27,7 @@ import com.google.gson.*; import com.zigythebird.playeranimcore.PlayerAnimLib; import com.zigythebird.playeranimcore.animation.Animation; -import com.zigythebird.playeranimcore.animation.CustomAnimationBone; +import com.zigythebird.playeranimcore.animation.CustomModelBone; import com.zigythebird.playeranimcore.animation.ExtraAnimationData; import com.zigythebird.playeranimcore.animation.keyframe.BoneAnimation; import com.zigythebird.playeranimcore.animation.keyframe.Keyframe; @@ -63,7 +63,7 @@ public Animation deserialize(JsonElement json, Type typeOfT, JsonDeserialization Animation.Keyframes keyframes = context.deserialize(animationObj, Animation.Keyframes.class); Map parents = UniversalAnimLoader.getParents(JsonUtil.getAsJsonObject(animationObj, "parents", new JsonObject())); - Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(animationObj, "model", new JsonObject()), context); + Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(animationObj, "model", new JsonObject()), context); // Extra data ExtraAnimationData extraData = new ExtraAnimationData(); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java b/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java index 4331dcdf..64d7f01a 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/loading/UniversalAnimLoader.java @@ -3,7 +3,7 @@ import com.google.gson.*; import com.zigythebird.playeranimcore.PlayerAnimLib; import com.zigythebird.playeranimcore.animation.Animation; -import com.zigythebird.playeranimcore.animation.CustomAnimationBone; +import com.zigythebird.playeranimcore.animation.CustomModelBone; import com.zigythebird.playeranimcore.animation.ExtraAnimationData; import com.zigythebird.playeranimcore.animation.keyframe.event.data.CustomInstructionKeyframeData; import com.zigythebird.playeranimcore.animation.keyframe.event.data.ParticleKeyframeData; @@ -40,7 +40,7 @@ public static Map loadAnimations(InputStream resource) throws if (json.has("animations")) { Map animationMap = PlayerAnimLib.GSON.fromJson(json.get("animations"), PlayerAnimLib.ANIMATIONS_MAP_TYPE); if (json.has("model")) { - Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(json, "model", new JsonObject()), PlayerAnimLib.GSON::fromJson); + Map bones = UniversalAnimLoader.getModel(JsonUtil.getAsJsonObject(json, "model", new JsonObject()), PlayerAnimLib.GSON::fromJson); for (Animation animation : animationMap.values()) { if (animation.bones().isEmpty()) { animation.bones().putAll(bones); @@ -90,10 +90,10 @@ public static Map getParents(JsonObject parentsObj) { return parents; } - public static Map getModel(JsonObject modelObj, JsonDeserializationContext ctx) { - Map bones = new HashMap<>(modelObj.size()); + public static Map getModel(JsonObject modelObj, JsonDeserializationContext ctx) { + Map bones = new HashMap<>(modelObj.size()); for (Map.Entry entry : modelObj.entrySet()) { - bones.put(entry.getKey(), ctx.deserialize(entry.getValue(), CustomAnimationBone.class)); + bones.put(entry.getKey(), ctx.deserialize(entry.getValue(), CustomModelBone.class)); } return bones; } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinary.java b/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinary.java index 4c8522d8..631724f6 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinary.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinary.java @@ -1,7 +1,7 @@ package com.zigythebird.playeranimcore.network; import com.zigythebird.playeranimcore.animation.Animation; -import com.zigythebird.playeranimcore.animation.CustomAnimationBone; +import com.zigythebird.playeranimcore.animation.CustomModelBone; import com.zigythebird.playeranimcore.animation.ExtraAnimationData; import com.zigythebird.playeranimcore.animation.keyframe.BoneAnimation; import com.zigythebird.playeranimcore.animation.keyframe.Keyframe; @@ -181,7 +181,7 @@ public static Animation read(ByteBuf buf, int version) { } Animation.Keyframes keyFrames = readEventKeyframes(buf); - Map pivotBones = NetworkUtils.readMap(buf, ProtocolUtils::readString, byteBuf -> NetworkUtils.readCustomBone(byteBuf, version)); + Map pivotBones = NetworkUtils.readMap(buf, ProtocolUtils::readString, byteBuf -> NetworkUtils.readCustomBone(byteBuf, version)); Map parents = NetworkUtils.readMap(buf, ProtocolUtils::readString, ProtocolUtils::readString); return new Animation(data, length, loopType, boneAnimations, keyFrames, pivotBones, parents); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinaryV6.java b/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinaryV6.java index 45a5b526..4de15612 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinaryV6.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/AnimationBinaryV6.java @@ -1,7 +1,7 @@ package com.zigythebird.playeranimcore.network; import com.zigythebird.playeranimcore.animation.Animation; -import com.zigythebird.playeranimcore.animation.CustomAnimationBone; +import com.zigythebird.playeranimcore.animation.CustomModelBone; import com.zigythebird.playeranimcore.animation.ExtraAnimationData; import com.zigythebird.playeranimcore.animation.keyframe.BoneAnimation; import com.zigythebird.playeranimcore.animation.keyframe.Keyframe; @@ -76,7 +76,7 @@ static Animation read(ByteBuf buf, int version) { buf1 -> readBoneAnimation(buf1, isPlayerAnimator, version)); Animation.Keyframes keyFrames = AnimationBinary.readEventKeyframes(buf); - Map pivotBones = NetworkUtils.readMap(buf, ProtocolUtils::readString, byteBuf -> NetworkUtils.readCustomBone(byteBuf, version)); + Map pivotBones = NetworkUtils.readMap(buf, ProtocolUtils::readString, byteBuf -> NetworkUtils.readCustomBone(byteBuf, version)); Map parents = NetworkUtils.readMap(buf, ProtocolUtils::readString, ProtocolUtils::readString); return new Animation(data, length, loopType, boneAnimations, keyFrames, pivotBones, parents); diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java b/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java index d61dbda6..945ba63b 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java @@ -1,6 +1,6 @@ package com.zigythebird.playeranimcore.network; -import com.zigythebird.playeranimcore.animation.CustomAnimationBone; +import com.zigythebird.playeranimcore.animation.CustomModelBone; import com.zigythebird.playeranimcore.math.Vec3f; import io.netty.buffer.ByteBuf; import team.unnamed.mocha.util.network.VarIntUtils; @@ -31,12 +31,12 @@ public static void writeMap(ByteBuf buf, Map map, BiConsumer Date: Sat, 28 Feb 2026 00:08:47 +0700 Subject: [PATCH 08/13] qweqwe --- .../playeranimcore/animation/layered/AnimationSnapshot.java | 6 ------ .../playeranimcore/animation/layered/IAnimation.java | 2 +- .../zigythebird/playeranim/animation/MinecraftModel.java | 3 +-- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationSnapshot.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationSnapshot.java index a81f715a..afc51c9d 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationSnapshot.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/AnimationSnapshot.java @@ -1,12 +1,10 @@ package com.zigythebird.playeranimcore.animation.layered; -import com.zigythebird.playeranimcore.bones.CustomBone; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import com.zigythebird.playeranimcore.bones.ToggleablePlayerAnimBone; import org.jetbrains.annotations.NotNull; import java.util.Map; -import java.util.function.Consumer; public record AnimationSnapshot(Map snapshots) implements IAnimation { @Override @@ -27,8 +25,4 @@ public void get3DTransform(@NotNull PlayerAnimBone bone) { "snapshots=" + snapshots + '}'; } - - @Override - public void collectModels(Consumer consumer) { - } } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/IAnimation.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/IAnimation.java index 6af8ac45..88272fff 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/IAnimation.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/layered/IAnimation.java @@ -92,5 +92,5 @@ default boolean canRemove() { return false; } - void collectModels(Consumer consumer); + default void collectModels(Consumer consumer) {} } diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java b/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java index dab4ffff..fa576a41 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java @@ -111,11 +111,10 @@ public T compute(SharedOperationKey key) { } private static TextureAtlasSprite loadAtlasSprite(String texture) throws IOException { - Identifier texId = PlayerAnimLibMod.id("dynamic/texture_" + TEXTURE_COUNTER.getAndIncrement()); - RenderSystem.assertOnRenderThread(); NativeImage image = NativeImage.read(Base64.getDecoder().decode(texture)); + Identifier texId = PlayerAnimLibMod.id("dynamic/texture_" + TEXTURE_COUNTER.getAndIncrement()); DynamicTexture dynTex = new DynamicTexture(texId::toString, image); Minecraft.getInstance().getTextureManager().register(texId, dynTex); From 3fbcde30588b7e06e744bc8a96cb74991c2d0604 Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Sat, 28 Feb 2026 00:16:31 +0700 Subject: [PATCH 09/13] move base64 decode --- .../animation/AnimationController.java | 2 +- .../playeranimcore/animation/CustomModelBone.java | 5 +++-- .../playeranim/animation/MinecraftModel.java | 13 +++++-------- .../animation/PlayerAnimationController.java | 4 ++-- 4 files changed, 11 insertions(+), 13 deletions(-) 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 9b2f0fee..d1abd0b2 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationController.java @@ -751,7 +751,7 @@ else if (pivotBones.containsKey(entry.getKey())) this.postAnimationSetupConsumer.accept((name) -> bones.getOrDefault(name, null)); } - protected abstract @Nullable PlatformModel loadCustomModel(@Nullable String texture, @Nullable JsonArray elements); + protected abstract @Nullable PlatformModel loadCustomModel(byte @Nullable [] texture, @Nullable JsonArray elements); @Override public void collectModels(Consumer consumer) { diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomModelBone.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomModelBone.java index cd38045d..196100d4 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomModelBone.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/CustomModelBone.java @@ -5,15 +5,16 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Type; +import java.util.Base64; -public record CustomModelBone(Vec3f pivot, @Nullable String texture, @Nullable JsonArray elements) { +public record CustomModelBone(Vec3f pivot, byte @Nullable [] texture, @Nullable JsonArray elements) { public static class Deserializer implements JsonDeserializer { @Override public CustomModelBone deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) throws JsonParseException { JsonObject obj = json.getAsJsonObject(); return new CustomModelBone( ctx.deserialize(obj.get("pivot"), Vec3f.class), - obj.has("texture") ? obj.get("texture").getAsString() : null, + obj.has("texture") ? Base64.getDecoder().decode(obj.get("texture").getAsString()) : null, obj.getAsJsonArray("elements") ); } diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java b/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java index fa576a41..0beac1e6 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java @@ -16,14 +16,11 @@ import net.minecraft.client.resources.metadata.animation.FrameSize; import net.minecraft.client.resources.model.*; import net.minecraft.resources.Identifier; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.joml.Vector3fc; import org.jspecify.annotations.NonNull; import java.io.IOException; import java.util.ArrayList; -import java.util.Base64; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -34,7 +31,7 @@ public class MinecraftModel implements PlatformModel, MaterialBaker, ModelBaker, private final RenderType renderType; private final QuadCollection geometry; - public MinecraftModel(@Nullable String texture, @NotNull JsonArray elements) throws IOException { + public MinecraftModel(byte @NonNull [] texture, @NonNull JsonArray elements) throws IOException { this.sprite = loadAtlasSprite(texture); this.renderType = RenderTypes.entityCutoutCull(this.sprite.atlasLocation()); @@ -45,12 +42,12 @@ public MinecraftModel(@Nullable String texture, @NotNull JsonArray elements) thr this.geometry = SimpleUnbakedGeometry.bake(blockElements, TextureSlots.EMPTY, this, BlockModelRotation.IDENTITY, this); } - @NotNull + @NonNull public QuadCollection getGeometry() { return this.geometry; } - @NotNull + @NonNull public RenderType getRenderType() { return this.renderType; } @@ -110,9 +107,9 @@ public T compute(SharedOperationKey key) { return key.compute(this); } - private static TextureAtlasSprite loadAtlasSprite(String texture) throws IOException { + private static TextureAtlasSprite loadAtlasSprite(byte[] texture) throws IOException { RenderSystem.assertOnRenderThread(); - NativeImage image = NativeImage.read(Base64.getDecoder().decode(texture)); + NativeImage image = NativeImage.read(texture); Identifier texId = PlayerAnimLibMod.id("dynamic/texture_" + TEXTURE_COUNTER.getAndIncrement()); DynamicTexture dynTex = new DynamicTexture(texId::toString, image); diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java b/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java index 6d08f0ba..3f49847b 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/animation/PlayerAnimationController.java @@ -96,8 +96,8 @@ public boolean replaceAnimationWithFade(@NotNull AbstractFadeModifier fadeModifi } @Override - protected @Nullable PlatformModel loadCustomModel(@Nullable String texture, @Nullable JsonArray elements) { - if (elements == null || elements.isEmpty()) return null; + protected @Nullable PlatformModel loadCustomModel(byte @Nullable [] texture, @Nullable JsonArray elements) { + if (texture == null || elements == null || elements.isEmpty()) return null; try { return new MinecraftModel(texture, elements); } catch (Exception e) { From 7e88bcb79ffde19fceac0529c3d76c59069af94c Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Sat, 28 Feb 2026 00:43:31 +0700 Subject: [PATCH 10/13] Update HumanoidAnimationController.java --- .../animation/HumanoidAnimationController.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/HumanoidAnimationController.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/HumanoidAnimationController.java index 259ab2ac..5771a548 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/HumanoidAnimationController.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/HumanoidAnimationController.java @@ -24,9 +24,12 @@ package com.zigythebird.playeranimcore.animation; +import com.google.gson.JsonArray; +import com.zigythebird.playeranimcore.bindings.PlatformModel; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; import com.zigythebird.playeranimcore.math.Vec3f; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import team.unnamed.mocha.MochaEngine; import java.util.ArrayList; @@ -34,7 +37,7 @@ import java.util.Map; import java.util.function.Function; -public abstract class HumanoidAnimationController extends AnimationController { +public class HumanoidAnimationController extends AnimationController { /** * Bone pivot point positions used to apply custom pivot point translations. */ @@ -129,4 +132,9 @@ public PlayerAnimBone get3DTransformRaw(@NotNull PlayerAnimBone bone) { } return bone; } + + @Override + protected @Nullable PlatformModel loadCustomModel(byte @Nullable [] texture, @Nullable JsonArray elements) { + return null; + } } From 5b18ce26b9911a9dad1dac4677810972cb01ebb4 Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Sat, 28 Feb 2026 01:41:13 +0700 Subject: [PATCH 11/13] network --- .../network/CustomModelUtils.java | 232 +++++++++++ .../playeranimcore/network/NetworkUtils.java | 48 ++- .../network/CustomModelBinaryTest.java | 365 ++++++++++++++++++ core/src/test/resources/cmm_bone_test.json | 25 ++ 4 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/zigythebird/playeranimcore/network/CustomModelUtils.java create mode 100644 core/src/test/java/com/zigythebird/playeranimcore/network/CustomModelBinaryTest.java create mode 100644 core/src/test/resources/cmm_bone_test.json diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/CustomModelUtils.java b/core/src/main/java/com/zigythebird/playeranimcore/network/CustomModelUtils.java new file mode 100644 index 00000000..4d738cfb --- /dev/null +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/CustomModelUtils.java @@ -0,0 +1,232 @@ +package com.zigythebird.playeranimcore.network; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.netty.buffer.ByteBuf; +import team.unnamed.mocha.util.network.ProtocolUtils; +import team.unnamed.mocha.util.network.VarIntUtils; + +class CustomModelUtils { + private static final String[] DIRECTION_NAMES = {"down", "up", "north", "south", "west", "east"}; + private static final String[] AXIS_NAMES = {"x", "y", "z"}; + + /** + * ── BlockElement ── + * elementFlags (varint): + * bit 0: hasFrom (almost always set → low bit) + * bit 1: hasTo (almost always set → low bit) + * bit 2: hasRotation + * bit 3: noShade (bit set = shade is false, default true → 0) + * bits 4-7: lightEmission (0-15) + */ + static void writeBlockElement(ByteBuf buf, JsonObject element, int version) { + boolean hasFrom = element.has("from"); + boolean hasTo = element.has("to"); + boolean hasRotation = element.has("rotation"); + boolean noShade = element.has("shade") && !element.get("shade").getAsBoolean(); + int lightEmission = element.has("light_emission") ? element.get("light_emission").getAsInt() : 0; + + int flags = 0; + if (hasFrom) flags |= 1; + if (hasTo) flags |= 2; + if (hasRotation) flags |= 4; + if (noShade) flags |= 8; + flags |= (lightEmission & 0xF) << 4; + VarIntUtils.writeVarInt(buf, flags); + + if (hasFrom) writeJsonVec3f(buf, element.getAsJsonArray("from")); + if (hasTo) writeJsonVec3f(buf, element.getAsJsonArray("to")); + + JsonObject faces = element.has("faces") ? element.getAsJsonObject("faces") : null; + int facesBitmask = 0; + if (faces != null) { + for (int d = 0; d < 6; d++) { + if (faces.has(DIRECTION_NAMES[d])) facesBitmask |= (1 << d); + } + } + VarIntUtils.writeVarInt(buf, facesBitmask); + + for (int d = 0; d < 6; d++) { + if ((facesBitmask & (1 << d)) == 0) continue; + writeBlockElementFace(buf, faces.getAsJsonObject(DIRECTION_NAMES[d]), version); + } + + if (hasRotation) { + writeBlockElementRotation(buf, element.getAsJsonObject("rotation"), version); + } + } + + static JsonObject readBlockElement(ByteBuf buf, int version) { + JsonObject element = new JsonObject(); + + int flags = VarIntUtils.readVarInt(buf); + boolean hasFrom = (flags & 1) != 0; + boolean hasTo = (flags & 2) != 0; + boolean hasRotation = (flags & 4) != 0; + boolean noShade = (flags & 8) != 0; + int lightEmission = (flags >> 4) & 0xF; + + if (hasFrom) element.add("from", readJsonVec3f(buf)); + if (hasTo) element.add("to", readJsonVec3f(buf)); + if (noShade) element.addProperty("shade", false); + if (lightEmission != 0) element.addProperty("light_emission", lightEmission); + + int facesBitmask = VarIntUtils.readVarInt(buf); + if (facesBitmask != 0) { + JsonObject faces = new JsonObject(); + for (int d = 0; d < 6; d++) { + if ((facesBitmask & (1 << d)) == 0) continue; + faces.add(DIRECTION_NAMES[d], readBlockElementFace(buf, version)); + } + element.add("faces", faces); + } + + if (hasRotation) { + element.add("rotation", readBlockElementRotation(buf, version)); + } + + return element; + } + + /** + * ── BlockElementFace ── + * faceFlags (varint): + * bit 0: hasTexture + * bit 1: hasUVs + * bit 2: hasTintIndex + * bits 3-5: cullForDirection (0-5=dir, 7=none) + * bit 6: hasRotation + */ + private static void writeBlockElementFace(ByteBuf buf, JsonObject face, int version) { + boolean hasTexture = face.has("texture"); + boolean hasUVs = face.has("uv"); + int tintIndex = face.has("tintindex") ? face.get("tintindex").getAsInt() : -1; + boolean hasTint = tintIndex != -1; + + int cullDir = 7; + if (face.has("cullface")) { + String cullName = face.get("cullface").getAsString(); + for (int i = 0; i < 6; i++) { + if (DIRECTION_NAMES[i].equals(cullName)) { cullDir = i; break; } + } + } + boolean hasRotation = face.has("rotation"); + + int faceFlags = (hasTexture ? 1 : 0) | (hasUVs ? (1 << 1) : 0) | (hasTint ? (1 << 2) : 0) + | ((cullDir & 0x7) << 3) | (hasRotation ? (1 << 6) : 0); + VarIntUtils.writeVarInt(buf, faceFlags); + + if (hasTexture) ProtocolUtils.writeString(buf, face.get("texture").getAsString()); + if (hasTint) VarIntUtils.writeVarInt(buf, tintIndex); + + if (hasUVs) { + JsonArray uv = face.getAsJsonArray("uv"); + buf.writeFloat(uv.get(0).getAsFloat()); + buf.writeFloat(uv.get(1).getAsFloat()); + buf.writeFloat(uv.get(2).getAsFloat()); + buf.writeFloat(uv.get(3).getAsFloat()); + } + + if (hasRotation) VarIntUtils.writeVarInt(buf, face.get("rotation").getAsInt()); + } + + private static JsonObject readBlockElementFace(ByteBuf buf, int version) { + int faceFlags = VarIntUtils.readVarInt(buf); + boolean hasTexture = (faceFlags & 1) != 0; + boolean hasUVs = (faceFlags & (1 << 1)) != 0; + boolean hasTint = (faceFlags & (1 << 2)) != 0; + int cullDir = (faceFlags >> 3) & 0x7; + boolean hasRotation = (faceFlags & (1 << 6)) != 0; + + JsonObject face = new JsonObject(); + + if (hasTexture) face.addProperty("texture", ProtocolUtils.readString(buf)); + if (cullDir < 6) face.addProperty("cullface", DIRECTION_NAMES[cullDir]); + if (hasTint) face.addProperty("tintindex", VarIntUtils.readVarInt(buf)); + + if (hasUVs) { + JsonArray uv = new JsonArray(4); + uv.add(buf.readFloat()); + uv.add(buf.readFloat()); + uv.add(buf.readFloat()); + uv.add(buf.readFloat()); + face.add("uv", uv); + } + + if (hasRotation) face.addProperty("rotation", VarIntUtils.readVarInt(buf)); + + return face; + } + + /** + * ── BlockElementRotation ── + * rotFlags (varint): bit0=isEuler, bit1=rescale + */ + private static void writeBlockElementRotation(ByteBuf buf, JsonObject rotation, int version) { + writeJsonVec3f(buf, rotation.getAsJsonArray("origin")); + + // MC logic: euler when axis/angle are absent + boolean isEuler = !rotation.has("axis") && !rotation.has("angle"); + boolean rescale = rotation.has("rescale") && rotation.get("rescale").getAsBoolean(); + + int rotFlags = 0; + if (isEuler) rotFlags |= 1; + if (rescale) rotFlags |= 2; + VarIntUtils.writeVarInt(buf, rotFlags); + + if (isEuler) { + buf.writeFloat(rotation.has("x") ? rotation.get("x").getAsFloat() : 0f); + buf.writeFloat(rotation.has("y") ? rotation.get("y").getAsFloat() : 0f); + buf.writeFloat(rotation.has("z") ? rotation.get("z").getAsFloat() : 0f); + } else { + String axisName = rotation.get("axis").getAsString(); + int axis = 0; + for (int i = 0; i < 3; i++) { + if (AXIS_NAMES[i].equals(axisName)) { axis = i; break; } + } + VarIntUtils.writeVarInt(buf, axis); + buf.writeFloat(rotation.get("angle").getAsFloat()); + } + } + + private static JsonObject readBlockElementRotation(ByteBuf buf, int version) { + JsonObject rotation = new JsonObject(); + rotation.add("origin", readJsonVec3f(buf)); + + int rotFlags = VarIntUtils.readVarInt(buf); + boolean isEuler = (rotFlags & 1) != 0; + boolean rescale = (rotFlags & 2) != 0; + + if (isEuler) { + float x = buf.readFloat(); + float y = buf.readFloat(); + float z = buf.readFloat(); + rotation.addProperty("x", x); + rotation.addProperty("y", y); + rotation.addProperty("z", z); + } else { + int axis = VarIntUtils.readVarInt(buf); + float angle = buf.readFloat(); + rotation.addProperty("axis", AXIS_NAMES[axis]); + rotation.addProperty("angle", angle); + } + + if (rescale) rotation.addProperty("rescale", true); + + return rotation; + } + + private static void writeJsonVec3f(ByteBuf buf, JsonArray arr) { + buf.writeFloat(arr.get(0).getAsFloat()); + buf.writeFloat(arr.get(1).getAsFloat()); + buf.writeFloat(arr.get(2).getAsFloat()); + } + + private static JsonArray readJsonVec3f(ByteBuf buf) { + JsonArray arr = new JsonArray(3); + arr.add(buf.readFloat()); + arr.add(buf.readFloat()); + arr.add(buf.readFloat()); + return arr; + } +} diff --git a/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java b/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java index 945ba63b..6bb1d0a7 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/network/NetworkUtils.java @@ -1,5 +1,7 @@ package com.zigythebird.playeranimcore.network; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.zigythebird.playeranimcore.animation.CustomModelBone; import com.zigythebird.playeranimcore.math.Vec3f; import io.netty.buffer.ByteBuf; @@ -31,13 +33,57 @@ public static void writeMap(ByteBuf buf, Map map, BiConsumer= 6) { + int boneFlags = VarIntUtils.readVarInt(buf); + + if ((boneFlags & 1) != 0) { + int len = VarIntUtils.readVarInt(buf); + texture = new byte[len]; + buf.readBytes(texture); + } + + if ((boneFlags & 2) != 0) { + int count = VarIntUtils.readVarInt(buf); + elements = new JsonArray(count); + for (int i = 0; i < count; i++) { + elements.add(CustomModelUtils.readBlockElement(buf, version)); + } + } + } + + return new CustomModelBone(pivot, texture, elements); } public static void writeCustomBone(ByteBuf buf, CustomModelBone bone, int version) { writeVec3f(buf, bone.pivot()); + + if (version >= 6) { + int boneFlags = 0; + if (bone.texture() != null) boneFlags |= 1; + if (bone.elements() != null) boneFlags |= 2; + VarIntUtils.writeVarInt(buf, boneFlags); + + if (bone.texture() != null) { + VarIntUtils.writeVarInt(buf, bone.texture().length); + buf.writeBytes(bone.texture()); + } + + if (bone.elements() != null) { + VarIntUtils.writeVarInt(buf, bone.elements().size()); + for (JsonElement el : bone.elements()) { + CustomModelUtils.writeBlockElement(buf, el.getAsJsonObject(), version); + } + } + } } public static Vec3f readVec3f(ByteBuf buf) { diff --git a/core/src/test/java/com/zigythebird/playeranimcore/network/CustomModelBinaryTest.java b/core/src/test/java/com/zigythebird/playeranimcore/network/CustomModelBinaryTest.java new file mode 100644 index 00000000..460531c7 --- /dev/null +++ b/core/src/test/java/com/zigythebird/playeranimcore/network/CustomModelBinaryTest.java @@ -0,0 +1,365 @@ +package com.zigythebird.playeranimcore.network; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.zigythebird.playeranimcore.animation.Animation; +import com.zigythebird.playeranimcore.animation.CustomModelBone; +import com.zigythebird.playeranimcore.loading.UniversalAnimLoader; +import com.zigythebird.playeranimcore.math.Vec3f; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +public class CustomModelBinaryTest { + + private static JsonArray vec3(float x, float y, float z) { + JsonArray arr = new JsonArray(3); + arr.add(x); + arr.add(y); + arr.add(z); + return arr; + } + + private static JsonArray uv(float minU, float minV, float maxU, float maxV) { + JsonArray arr = new JsonArray(4); + arr.add(minU); + arr.add(minV); + arr.add(maxU); + arr.add(maxV); + return arr; + } + + private static JsonObject makeFace(String texture, String cullface, float[] uvs, int tintIndex, int rotation) { + JsonObject face = new JsonObject(); + if (texture != null) face.addProperty("texture", texture); + if (cullface != null) face.addProperty("cullface", cullface); + if (uvs != null) face.add("uv", uv(uvs[0], uvs[1], uvs[2], uvs[3])); + if (tintIndex != -1) face.addProperty("tintindex", tintIndex); + if (rotation != 0) face.addProperty("rotation", rotation); + return face; + } + + private static void assertRoundTrip(CustomModelBone bone, int version) { + ByteBuf buf = Unpooled.buffer(); + NetworkUtils.writeCustomBone(buf, bone, version); + CustomModelBone read = NetworkUtils.readCustomBone(buf, version); + + Assertions.assertEquals(bone.pivot(), read.pivot(), "pivot mismatch"); + Assertions.assertArrayEquals(bone.texture(), read.texture(), "texture mismatch"); + + if (bone.elements() == null) { + Assertions.assertNull(read.elements(), "expected null elements"); + } else { + Assertions.assertNotNull(read.elements(), "expected non-null elements"); + Assertions.assertEquals(bone.elements().size(), read.elements().size(), "elements count mismatch"); + for (int i = 0; i < bone.elements().size(); i++) { + Assertions.assertEquals( + bone.elements().get(i), + read.elements().get(i), + "element " + i + " mismatch" + ); + } + } + + Assertions.assertFalse(buf.isReadable(), "buffer has leftover bytes"); + buf.release(); + } + + // ── Tests ── + + @Test + @DisplayName("Bone with only pivot (version < 6)") + void pivotOnlyOldVersion() { + CustomModelBone bone = new CustomModelBone(new Vec3f(1, 2, 3), null, null); + assertRoundTrip(bone, 5); + } + + @Test + @DisplayName("Bone with only pivot (version 6)") + void pivotOnly() { + CustomModelBone bone = new CustomModelBone(new Vec3f(1, 2, 3), null, null); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Bone with texture bytes") + void boneWithTexture() { + byte[] tex = {0x00, 0x01, 0x02, (byte) 0xFF}; + CustomModelBone bone = new CustomModelBone(new Vec3f(0, 0, 0), tex, null); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Full element: from, to, 6 faces with all fields") + void fullElement() { + JsonObject element = new JsonObject(); + element.add("from", vec3(0, 0, 0)); + element.add("to", vec3(16, 16, 16)); + + JsonObject faces = new JsonObject(); + String[] dirs = {"down", "up", "north", "south", "west", "east"}; + for (String dir : dirs) { + faces.add(dir, makeFace("#all", dir, new float[]{0, 0, 16, 16}, -1, 0)); + } + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(8, 8, 8), null, elements); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Element with shade=false and light_emission") + void shadeAndLightEmission() { + JsonObject element = new JsonObject(); + element.add("from", vec3(0, 0, 0)); + element.add("to", vec3(8, 8, 8)); + element.addProperty("shade", false); + element.addProperty("light_emission", 15); + + JsonObject faces = new JsonObject(); + faces.add("up", makeFace("#top", null, null, -1, 0)); + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(0, 0, 0), null, elements); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Face with tintindex, rotation, UV") + void faceAllFields() { + JsonObject element = new JsonObject(); + element.add("from", vec3(0, 0, 0)); + element.add("to", vec3(16, 16, 16)); + + JsonObject faces = new JsonObject(); + faces.add("north", makeFace("#side", "north", new float[]{0, 0, 16, 16}, 0, 270)); + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(8, 8, 8), null, elements); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Face without texture (minimal)") + void faceNoTexture() { + JsonObject element = new JsonObject(); + element.add("from", vec3(0, 0, 0)); + element.add("to", vec3(16, 16, 16)); + + JsonObject faces = new JsonObject(); + JsonObject face = new JsonObject(); + face.add("uv", uv(0, 0, 16, 16)); + faces.add("up", face); + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(0, 0, 0), null, elements); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Single-axis rotation with rescale") + void singleAxisRotation() { + JsonObject element = new JsonObject(); + element.add("from", vec3(0, 0, 0)); + element.add("to", vec3(16, 16, 16)); + + JsonObject rot = new JsonObject(); + rot.add("origin", vec3(8, 8, 8)); + rot.addProperty("axis", "y"); + rot.addProperty("angle", 22.5f); + rot.addProperty("rescale", true); + element.add("rotation", rot); + + JsonObject faces = new JsonObject(); + faces.add("north", makeFace("#side", null, null, -1, 0)); + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(8, 8, 8), null, elements); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Euler XYZ rotation") + void eulerRotation() { + JsonObject element = new JsonObject(); + element.add("from", vec3(0, 0, 0)); + element.add("to", vec3(16, 16, 16)); + + JsonObject rot = new JsonObject(); + rot.add("origin", vec3(8, 8, 8)); + rot.addProperty("x", 45f); + rot.addProperty("y", 0f); + rot.addProperty("z", -30f); + element.add("rotation", rot); + + JsonObject faces = new JsonObject(); + faces.add("up", makeFace("#top", null, null, -1, 0)); + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(0, 0, 0), null, elements); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Element without from/to (minimal)") + void elementMinimal() { + JsonObject element = new JsonObject(); + // no from, no to, no faces, no rotation + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(0, 0, 0), null, elements); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("Multiple elements") + void multipleElements() { + JsonArray elements = new JsonArray(); + + for (int i = 0; i < 3; i++) { + JsonObject element = new JsonObject(); + element.add("from", vec3(i, i, i)); + element.add("to", vec3(i + 8, i + 8, i + 8)); + + JsonObject faces = new JsonObject(); + faces.add("north", makeFace("#tex" + i, "north", new float[]{0, 0, 16, 16}, -1, 0)); + element.add("faces", faces); + + elements.add(element); + } + + CustomModelBone bone = new CustomModelBone(new Vec3f(8, 8, 8), new byte[]{1, 2, 3}, elements); + assertRoundTrip(bone, 6); + } + + @Test + @DisplayName("All directions for cullface") + void allCullfaceDirections() { + String[] dirs = {"down", "up", "north", "south", "west", "east"}; + + for (String dir : dirs) { + JsonObject element = new JsonObject(); + element.add("from", vec3(0, 0, 0)); + element.add("to", vec3(16, 16, 16)); + + JsonObject faces = new JsonObject(); + faces.add(dir, makeFace("#tex", dir, null, -1, 0)); + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(0, 0, 0), null, elements); + assertRoundTrip(bone, 6); + } + } + + @Test + @DisplayName("All axis for single-axis rotation") + void allAxes() { + String[] axes = {"x", "y", "z"}; + + for (String axis : axes) { + JsonObject element = new JsonObject(); + element.add("from", vec3(0, 0, 0)); + element.add("to", vec3(16, 16, 16)); + + JsonObject rot = new JsonObject(); + rot.add("origin", vec3(8, 8, 8)); + rot.addProperty("axis", axis); + rot.addProperty("angle", 45f); + element.add("rotation", rot); + + JsonObject faces = new JsonObject(); + faces.add("up", makeFace("#top", null, null, -1, 0)); + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(0, 0, 0), null, elements); + assertRoundTrip(bone, 6); + } + } + + @Test + @DisplayName("Real parsed bone from cmm_bone_test.json") + void realParsedBone() throws IOException { + try (InputStream is = CustomModelBinaryTest.class.getResourceAsStream("/cmm_bone_test.json")) { + Assertions.assertNotNull(is, "cmm_bone_test.json not found"); + Map animations = UniversalAnimLoader.loadAnimations(is); + + Assertions.assertFalse(animations.isEmpty(), "no animations loaded"); + + Animation animation = animations.values().iterator().next(); + Assertions.assertFalse(animation.bones().isEmpty(), "no bones in animation"); + + for (Map.Entry entry : animation.bones().entrySet()) { + CustomModelBone bone = entry.getValue(); + + Assertions.assertNotNull(bone.pivot(), "pivot is null for bone " + entry.getKey()); + Assertions.assertNotNull(bone.texture(), "texture is null for bone " + entry.getKey()); + Assertions.assertNotNull(bone.elements(), "elements is null for bone " + entry.getKey()); + Assertions.assertFalse(bone.elements().isEmpty(), "elements empty for bone " + entry.getKey()); + + assertRoundTrip(bone, 6); + } + } + } + + @Test + @DisplayName("Buffer fully consumed after read") + void bufferFullyConsumed() { + JsonObject element = new JsonObject(); + element.add("from", vec3(1, 2, 3)); + element.add("to", vec3(4, 5, 6)); + element.addProperty("shade", false); + element.addProperty("light_emission", 7); + + JsonObject rot = new JsonObject(); + rot.add("origin", vec3(0, 0, 0)); + rot.addProperty("x", 10f); + rot.addProperty("y", 20f); + rot.addProperty("z", 30f); + rot.addProperty("rescale", true); + element.add("rotation", rot); + + JsonObject faces = new JsonObject(); + faces.add("down", makeFace("#bottom", "down", new float[]{0, 0, 16, 16}, 2, 90)); + faces.add("up", makeFace("#top", "up", new float[]{0, 0, 16, 16}, 0, 180)); + faces.add("north", makeFace("#side", "north", new float[]{0, 0, 16, 16}, -1, 0)); + element.add("faces", faces); + + JsonArray elements = new JsonArray(); + elements.add(element); + + CustomModelBone bone = new CustomModelBone(new Vec3f(4, 5, 6), new byte[]{10, 20, 30}, elements); + assertRoundTrip(bone, 6); + } +} diff --git a/core/src/test/resources/cmm_bone_test.json b/core/src/test/resources/cmm_bone_test.json new file mode 100644 index 00000000..621dd819 --- /dev/null +++ b/core/src/test/resources/cmm_bone_test.json @@ -0,0 +1,25 @@ +{ + "format_version": "1.8.0", + "model": { + "cmm": { + "pivot": [ 0, 12, 0 ], + "texture": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAfUlEQVR4Xo1QiRHAIAhzUAZgC7foAg5KjSkq1OsVr1qS8Jby18TE1PivBi/R1a4JwmtRIp1uHUYOHeIlnxEOQoyz5ZBB40MOxNNPAsL6vKEIBIxBHx5/ECzqIHBwFQg9+BT7XeOguUTYw56DL5p9LZvrxpggE00DzJOZD7sBRaHjpRD91mQAAAAASUVORK5CYII=", + "elements": [ + { + "from": [ 0, 0.25, 0 ], + "to": [ 16, 0.25, 16 ], + "faces": { + "down": { "uv": [ 0, 16, 16, 0 ], "texture": "#texture", "tintindex": 0 }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "tintindex": 0 } + } + } + ] + } + }, + "animations": { + "cmm_bone_test": { + "loop": false, + "bones": {} + } + } +} From 42e45b8bdab76b636ede4af6dbdd435f297f7afd Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Wed, 4 Mar 2026 03:30:51 +0700 Subject: [PATCH 12/13] idk --- .../com/zigythebird/playeranim/animation/MinecraftModel.java | 2 +- .../zigythebird/playeranim/mixin/LivingEntityRendererMixin.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java b/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java index 0beac1e6..c9ebf1c0 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/animation/MinecraftModel.java @@ -33,7 +33,7 @@ public class MinecraftModel implements PlatformModel, MaterialBaker, ModelBaker, public MinecraftModel(byte @NonNull [] texture, @NonNull JsonArray elements) throws IOException { this.sprite = loadAtlasSprite(texture); - this.renderType = RenderTypes.entityCutoutCull(this.sprite.atlasLocation()); + this.renderType = RenderTypes.entityTranslucent(this.sprite.atlasLocation()); List blockElements = new ArrayList<>(); for (JsonElement element : elements) { diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java index 2ce8b1ab..107aa2ce 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java @@ -93,6 +93,8 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm QuadCollection bakedPart = mcModel.getGeometry(); poseStack.pushPose(); + poseStack.scale(-1, -1, 1); + poseStack.translate(-0.5F, 0, 0); animationPlayer.get3DTransform(bone); RenderUtil.translateMatrixToBone(poseStack, bone); From 0335f1bca1449861e0230b540ff9a0dd89302404 Mon Sep 17 00:00:00 2001 From: dima_dencep Date: Thu, 5 Mar 2026 02:45:41 +0700 Subject: [PATCH 13/13] Fix pivots --- .../playeranimcore/math/Vec3f.java | 4 ++++ .../mixin/LivingEntityRendererMixin.java | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/zigythebird/playeranimcore/math/Vec3f.java b/core/src/main/java/com/zigythebird/playeranimcore/math/Vec3f.java index 02992c5f..26ba2a44 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/math/Vec3f.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/math/Vec3f.java @@ -20,6 +20,10 @@ public Vec3f mul(float scalar) { return new Vec3f(this.x * scalar, this.y * scalar, this.z * scalar); } + public Vec3f div(float scalar) { + return new Vec3f(this.x / scalar, this.y / scalar, this.z / scalar); + } + /** * Add two vectors * diff --git a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java index 107aa2ce..97cf31bb 100644 --- a/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java +++ b/minecraft/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java @@ -32,21 +32,30 @@ import com.zigythebird.playeranim.util.RenderUtil; import com.zigythebird.playeranimcore.bindings.PlatformModel; import com.zigythebird.playeranimcore.bones.PlayerAnimBone; +import com.zigythebird.playeranimcore.util.MatrixUtil; +import net.minecraft.client.model.EntityModel; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.client.renderer.entity.LivingEntityRenderer; import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.renderer.state.CameraRenderState; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.resources.model.QuadCollection; import net.minecraft.core.Direction; +import net.minecraft.world.entity.LivingEntity; 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(LivingEntityRenderer.class) -public class LivingEntityRendererMixin { +public abstract class LivingEntityRendererMixin> extends EntityRenderer { + protected LivingEntityRendererMixin(EntityRendererProvider.Context context) { + super(context); + } + @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 IAvatarAnimationState avatarRenderState) { @@ -75,7 +84,8 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm 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/SubmitNodeCollector;submitModel(Lnet/minecraft/client/model/Model;Ljava/lang/Object;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/rendertype/RenderType;IIILnet/minecraft/client/renderer/texture/TextureAtlasSprite;ILnet/minecraft/client/renderer/feature/ModelFeatureRenderer$CrumblingOverlay;)V" + target = "Lnet/minecraft/client/renderer/SubmitNodeCollector;submitModel(Lnet/minecraft/client/model/Model;Ljava/lang/Object;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/rendertype/RenderType;IIILnet/minecraft/client/renderer/texture/TextureAtlasSprite;ILnet/minecraft/client/renderer/feature/ModelFeatureRenderer$CrumblingOverlay;)V", + shift = At.Shift.AFTER ) ) public void pal$renderCustomModels(S state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState camera, CallbackInfo ci) { @@ -86,6 +96,10 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm int lightCoords = state.lightCoords; + poseStack.pushPose(); + poseStack.scale(-1.0F, -1.0F, 1.0F); + poseStack.translate(-0.53F, -1.501F, -0.53F); + animationPlayer.collectModels(bone -> { PlatformModel platformModel = bone.getModel(); if (!(platformModel instanceof MinecraftModel mcModel)) return; @@ -93,8 +107,7 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm QuadCollection bakedPart = mcModel.getGeometry(); poseStack.pushPose(); - poseStack.scale(-1, -1, 1); - poseStack.translate(-0.5F, 0, 0); + MatrixUtil.translateToPivotPoint(poseStack.last().pose(), bone.getPivot().div(16)); // idk animationPlayer.get3DTransform(bone); RenderUtil.translateMatrixToBone(poseStack, bone); @@ -119,5 +132,7 @@ private void doTranslations(S livingEntityRenderState, PoseStack poseStack, Subm poseStack.popPose(); }); + + poseStack.popPose(); } }