Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON Entity Animations #1476

Merged
merged 27 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e6bc36a
Add JSON entity animation support
Gaming32 Aug 20, 2024
5b2ba99
Remove "spline" as an alias for "catmullrom"
Gaming32 Aug 20, 2024
f144016
Allow custom targets and interpolations to be registered
Gaming32 Aug 21, 2024
724c024
Remove JsonAnimator, add an AnimationHolder, and expose AnimationHold…
Gaming32 Aug 21, 2024
a02ae3f
Remove reference to JsonEA in javadoc
Gaming32 Aug 21, 2024
21d883d
Only hold a weak reference to unbound AnimationHolders
Gaming32 Aug 22, 2024
1f7c333
Use codecs for animations
Gaming32 Aug 22, 2024
1facc03
Fix build
Gaming32 Aug 22, 2024
7f7ff8d
Add javadoc and make slight improvements
Gaming32 Aug 22, 2024
0f5ac47
Add serialization round trip test
Gaming32 Aug 22, 2024
734e6c1
Run applyAllFormatting
Gaming32 Aug 22, 2024
796f358
Add some more documentation
Gaming32 Aug 23, 2024
198cc8b
Change missing animation warnings to be more robust
Gaming32 Aug 23, 2024
a230458
Error if a target could not be found from a channel target
Gaming32 Aug 23, 2024
17e1418
Register custom animation types in datagen
Gaming32 Aug 23, 2024
b0c51b9
strongHolderList -> strongHolderReferences
Gaming32 Aug 26, 2024
8f484e3
Insert javadocs indicating serialized representations of AnimationPar…
Gaming32 Aug 26, 2024
397062b
Include example of looping
Gaming32 Aug 26, 2024
27e176f
Apply formatting
Gaming32 Aug 26, 2024
6d44bee
Use KeyDispatchCodec directly
Gaming32 Aug 26, 2024
cbb6182
Move json animation type loading into ClientHooks.initClientHooks
Gaming32 Sep 4, 2024
8ece2a1
Merge branch '1.21.x' into json-entity-animations
Gaming32 Sep 4, 2024
a563035
Precompute keyframe codecs
Gaming32 Sep 4, 2024
9a3e45d
Check against duplicate registration for RegisterJsonAnimationTypesEvent
Gaming32 Sep 5, 2024
01b3b8b
Fix formatting
Gaming32 Sep 5, 2024
48892da
Don't use ModLoadingContext
Gaming32 Sep 5, 2024
f3924aa
Merge branch '1.21.x' into json-entity-animations
XFactHD Sep 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions patches/net/minecraft/client/model/HierarchicalModel.java.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--- a/net/minecraft/client/model/HierarchicalModel.java
+++ b/net/minecraft/client/model/HierarchicalModel.java
@@ -27,6 +_,10 @@
super(p_170623_);
}

+ protected static net.neoforged.neoforge.client.entity.animation.json.AnimationHolder getAnimation(ResourceLocation key) {
+ return net.neoforged.neoforge.client.entity.animation.json.AnimationLoader.INSTANCE.getAnimationHolder(key);
+ }
+
@Override
public void renderToBuffer(PoseStack p_170625_, VertexConsumer p_170626_, int p_170627_, int p_170628_, int p_350603_) {
this.root().render(p_170625_, p_170626_, p_170627_, p_170628_, p_350603_);
@@ -44,18 +_,34 @@
this.animate(p_233382_, p_233383_, p_233384_, 1.0F);
}

+ protected void animate(AnimationState animationState, net.neoforged.neoforge.client.entity.animation.json.AnimationHolder animation, float ageInTicks) {
+ this.animate(animationState, animation.get(), ageInTicks);
+ }
+
protected void animateWalk(AnimationDefinition p_268159_, float p_268057_, float p_268347_, float p_268138_, float p_268165_) {
long i = (long)(p_268057_ * 50.0F * p_268138_);
float f = Math.min(p_268347_ * p_268165_, 1.0F);
KeyframeAnimations.animate(this, p_268159_, i, f, ANIMATION_VECTOR_CACHE);
}

+ protected void animateWalk(net.neoforged.neoforge.client.entity.animation.json.AnimationHolder animation, float limbSwing, float limbSwingAmount, float maxAnimationSpeed, float animationScaleFactor) {
+ this.animateWalk(animation.get(), limbSwing, limbSwingAmount, maxAnimationSpeed, animationScaleFactor);
+ }
+
protected void animate(AnimationState p_233386_, AnimationDefinition p_233387_, float p_233388_, float p_233389_) {
p_233386_.updateTime(p_233388_, p_233389_);
p_233386_.ifStarted(p_233392_ -> KeyframeAnimations.animate(this, p_233387_, p_233392_.getAccumulatedTime(), 1.0F, ANIMATION_VECTOR_CACHE));
}

+ protected void animate(AnimationState animationState, net.neoforged.neoforge.client.entity.animation.json.AnimationHolder animation, float ageInTicks, float speed) {
+ this.animate(animationState, animation.get(), ageInTicks, speed);
+ }
+
protected void applyStatic(AnimationDefinition p_288996_) {
KeyframeAnimations.animate(this, p_288996_, 0L, 1.0F, ANIMATION_VECTOR_CACHE);
+ }
+
+ protected void applyStatic(net.neoforged.neoforge.client.entity.animation.json.AnimationHolder animation) {
+ this.applyStatic(animation.get());
}
}
10 changes: 6 additions & 4 deletions src/main/java/net/neoforged/neoforge/client/ClientHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
import net.neoforged.fml.ModLoader;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.asm.enumextension.ExtensionInfo;
import net.neoforged.neoforge.client.entity.animation.json.AnimationTypeManager;
import net.neoforged.neoforge.client.event.AddSectionGeometryEvent;
import net.neoforged.neoforge.client.event.CalculateDetachedCameraDistanceEvent;
import net.neoforged.neoforge.client.event.CalculatePlayerTurnEvent;
Expand Down Expand Up @@ -950,7 +951,7 @@ public static <T extends BlockEntity> boolean isBlockEntityRendererVisible(Block
/**
* Modify the position and UVs of the edge quads of generated item models to account for sprite expansion of the
* front and back quad. Fixes <a href="https://bugs.mojang.com/browse/MC-73186">MC-73186</a> on generated item models.
*
*
* @param elements The generated elements, may include the front and back face
* @param sprite The texture from which the elements were generated
* @return the original elements list
Expand Down Expand Up @@ -1036,13 +1037,14 @@ public static void initClientHooks(Minecraft mc, ReloadableResourceManager resou
PresetEditorManager.init();
MapDecorationRendererManager.init();
DimensionTransitionScreenManager.init();
AnimationTypeManager.init();
}

/**
* Fires {@link RenderFrameEvent.Pre}. Called just before {@link GameRenderer#render(float, long, boolean)} in {@link Minecraft#runTick(boolean)}.
* <p>
* Fired before the profiler section for "gameRenderer" is started.
*
*
* @param partialTick The current partial tick
*/
public static void fireRenderFramePre(DeltaTracker partialTick) {
Expand All @@ -1053,7 +1055,7 @@ public static void fireRenderFramePre(DeltaTracker partialTick) {
* Fires {@link RenderFrameEvent.Post}. Called just after {@link GameRenderer#render(float, long, boolean)} in {@link Minecraft#runTick(boolean)}.
* <p>
* Fired after the profiler section for "gameRenderer" is ended.
*
*
* @param partialTick The current partial tick
*/
public static void fireRenderFramePost(DeltaTracker partialRick) {
Expand Down Expand Up @@ -1088,7 +1090,7 @@ public static <T> RegistryLookup<T> resolveLookup(ResourceKey<? extends Registry
* Fires the {@link GatherEffectScreenTooltipsEvent} and returns the resulting tooltip lines.
* <p>
* Called from {@link EffectRenderingInventoryScreen#renderEffects} just before {@link GuiGraphics#renderTooltip(Font, List, Optional, int, int)} is called.
*
*
* @param screen The screen rendering the tooltip.
* @param effectInst The effect instance whose tooltip is being rendered.
* @param tooltip An immutable list containing the existing tooltip lines, which consist of the name and the duration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfigs;
import net.neoforged.neoforge.client.entity.animation.json.AnimationLoader;
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
import net.neoforged.neoforge.client.event.ModelEvent;
import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
Expand Down Expand Up @@ -76,6 +77,7 @@ static void onRegisterGeometryLoaders(ModelEvent.RegisterGeometryLoaders event)
@SubscribeEvent
static void onRegisterReloadListeners(RegisterClientReloadListenersEvent event) {
event.registerReloadListener(ObjLoader.INSTANCE);
event.registerReloadListener(AnimationLoader.INSTANCE);
}

@SubscribeEvent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.entity.animation;

import org.joml.Vector3f;

/**
* A function for transforming vectors into values that make sense to their keyframe's target.
*/
@FunctionalInterface
public interface AnimationKeyframeTarget {
Vector3f apply(float x, float y, float z);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.entity.animation;

import net.minecraft.client.animation.AnimationChannel;
import net.minecraft.client.animation.KeyframeAnimations;
import org.joml.Vector3f;

/**
* Wrapper for a {@link AnimationChannel.Target} and a way to transform a simple keyframe vector into a vector that
* makes sense for the given target.
*
* @param channelTarget The associated {@link AnimationChannel.Target}.
* @param keyframeTarget An {@link AnimationKeyframeTarget} that transforms simple vectors into ones that make sense
* for the {@link #channelTarget}.
* @param inverseKeyframeTarget The inverse function of {@link #keyframeTarget}, used for serialization.
Gaming32 marked this conversation as resolved.
Show resolved Hide resolved
*/
public record AnimationTarget(
AnimationChannel.Target channelTarget,
AnimationKeyframeTarget keyframeTarget,
AnimationKeyframeTarget inverseKeyframeTarget) {

public static final AnimationTarget POSITION = new AnimationTarget(
AnimationChannel.Targets.POSITION,
KeyframeAnimations::posVec,
KeyframeAnimations::posVec // It's its own inverse
);
public static final AnimationTarget ROTATION = new AnimationTarget(
AnimationChannel.Targets.ROTATION,
KeyframeAnimations::degreeVec,
AnimationTarget::inverseDegreeVec);
public static final AnimationTarget SCALE = new AnimationTarget(
AnimationChannel.Targets.SCALE,
KeyframeAnimations::scaleVec,
AnimationTarget::inverseScaleVec);
private static Vector3f inverseDegreeVec(float x, float y, float z) {
return new Vector3f(
x / (float) (Math.PI / 180.0),
y / (float) (Math.PI / 180.0),
z / (float) (Math.PI / 180.0));
}

private static Vector3f inverseScaleVec(double x, double y, double z) {
return new Vector3f((float) (x + 1f), (float) (y + 1f), (float) (z + 1f));
}

@Override
public boolean equals(Object obj) {
return this == obj;
}

@Override
public int hashCode() {
return System.identityHashCode(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.entity.animation.json;

import com.mojang.logging.LogUtils;
import java.util.Map;
import net.minecraft.client.animation.AnimationDefinition;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

/**
* Holds a single {@link AnimationDefinition} loaded from resource packs. Objects of this class will be automatically updated with new
* {@link AnimationDefinition}s on reload.
*/
public final class AnimationHolder {
public static final AnimationDefinition EMPTY_ANIMATION = new AnimationDefinition(0f, false, Map.of());

private static final Logger LOGGER = LogUtils.getLogger();

private final ResourceLocation key;
@Nullable
private AnimationDefinition value;
private boolean absentWarned;

AnimationHolder(ResourceLocation key) {
this.key = key;
}

void unbind() {
value = null;
absentWarned = false;
}

void bind(AnimationDefinition value) {
this.value = value;
}

/**
* Gets the key associated with this animation.
*/
public ResourceLocation key() {
return key;
}

/**
* Gets the currently loaded animation. If the animation has not been loaded, returns {@link #EMPTY_ANIMATION}.
*/
public AnimationDefinition get() {
final var result = value;
if (result == null) {
if (!absentWarned) {
absentWarned = true;
LOGGER.warn("Missing entity animation {}", key);
}
return EMPTY_ANIMATION;
}
return result;
}

/**
* Gets the currently loaded animation or null if it has not been loaded.
*/
@Nullable
public AnimationDefinition getOrNull() {
return value;
}

/**
* Returns whether the animation has been loaded.
*/
public boolean isBound() {
return value != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.entity.animation.json;

import com.google.common.collect.MapMaker;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.JsonOps;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.minecraft.client.animation.AnimationDefinition;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

/**
* A loader for entity animations written in JSON. You can also get parsed animations from this class.
*/
public final class AnimationLoader extends SimpleJsonResourceReloadListener {
private static final Logger LOGGER = LogUtils.getLogger();

public static final AnimationLoader INSTANCE = new AnimationLoader();

private final Map<ResourceLocation, AnimationHolder> animations = new MapMaker().weakValues().concurrencyLevel(1).makeMap();
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final List<AnimationHolder> strongHolderReferences = new ArrayList<>();

private AnimationLoader() {
super(new Gson(), "animations/entity");
}

/**
* Gets a loaded {@link AnimationDefinition} with the specified {@code key}.
*/
@Nullable
public AnimationDefinition getAnimation(ResourceLocation key) {
final var holder = animations.get(key);
return holder != null ? holder.getOrNull() : null;
}

/**
* Returns an {@link AnimationHolder} for an animation. If the specified animation has not been loaded, the holder
* will be unbound, but may be bound in the future.
*/
public AnimationHolder getAnimationHolder(ResourceLocation key) {
return animations.computeIfAbsent(key, AnimationHolder::new);
}

@Override
protected void apply(Map<ResourceLocation, JsonElement> animationJsons, ResourceManager resourceManager, ProfilerFiller profiler) {
animations.values().forEach(AnimationHolder::unbind);
strongHolderReferences.clear();
int loaded = 0;
for (final var entry : animationJsons.entrySet()) {
try {
final var animation = AnimationParser.CODEC.parse(JsonOps.INSTANCE, entry.getValue())
.getOrThrow(JsonParseException::new);
final var holder = getAnimationHolder(entry.getKey());
holder.bind(animation);
strongHolderReferences.add(holder);
loaded++;
} catch (Exception e) {
LOGGER.error("Failed to load animation {}", entry.getKey(), e);
}
}
LOGGER.info("Loaded {} entity animations", loaded);
}
}
Loading