From 6f9e469b3cc23103a17a65a339de749d4ed3bd7a Mon Sep 17 00:00:00 2001 From: baguchan Date: Sat, 14 Jun 2025 08:09:48 +0900 Subject: [PATCH] animation system complete! --- src/main/java/org/useless/DragonFly.java | 10 +- .../java/org/useless/DragonFlyClient.java | 38 +++ .../dragonfly/animation/Animation.java | 29 ++ .../dragonfly/animation/AnimationData.java | 29 ++ .../dragonfly/animation/KeyFrameData.java | 27 ++ .../mojang/EntityGeometryMojangData.java | 22 +- .../mojang/MojangAnimationDeserializer.java | 74 +++++ .../dragonfly/renderer/MobRenderer.java | 24 ++ .../useless/test_entity/MobRendererTest.java | 27 ++ .../org/useless/test_entity/ModEntity.java | 17 ++ .../java/org/useless/test_entity/TestMob.java | 11 + .../org/useless/util/AnimationHelper.java | 260 ++++++++++++++++ .../java/org/useless/util/AnimationState.java | 58 ++++ .../assets/dragonfly/animation/walk.json | 222 ++++++++++++++ .../dragonfly/models/entity/model.geo.json | 279 ++++++++++++++++++ .../dragonfly/models/entity/models.json | 5 + src/main/resources/fabric.mod.json | 3 + 17 files changed, 1115 insertions(+), 20 deletions(-) create mode 100644 src/main/java/org/useless/DragonFlyClient.java create mode 100644 src/main/java/org/useless/dragonfly/animation/Animation.java create mode 100644 src/main/java/org/useless/dragonfly/animation/AnimationData.java create mode 100644 src/main/java/org/useless/dragonfly/animation/KeyFrameData.java create mode 100644 src/main/java/org/useless/dragonfly/data/entity/mojang/MojangAnimationDeserializer.java create mode 100644 src/main/java/org/useless/test_entity/MobRendererTest.java create mode 100644 src/main/java/org/useless/test_entity/ModEntity.java create mode 100644 src/main/java/org/useless/test_entity/TestMob.java create mode 100644 src/main/java/org/useless/util/AnimationHelper.java create mode 100644 src/main/java/org/useless/util/AnimationState.java create mode 100644 src/main/resources/assets/dragonfly/animation/walk.json create mode 100644 src/main/resources/assets/dragonfly/models/entity/model.geo.json create mode 100644 src/main/resources/assets/dragonfly/models/entity/models.json diff --git a/src/main/java/org/useless/DragonFly.java b/src/main/java/org/useless/DragonFly.java index 7606546..60692f7 100644 --- a/src/main/java/org/useless/DragonFly.java +++ b/src/main/java/org/useless/DragonFly.java @@ -5,12 +5,14 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.useless.dragonfly.animation.Animation; import org.useless.dragonfly.data.block.mojang.BlockModelMojangData; import org.useless.dragonfly.data.block.mojang.state.AppliedData; import org.useless.dragonfly.data.block.mojang.state.BlockstateData; import org.useless.dragonfly.data.block.mojang.state.ModelPart; import org.useless.dragonfly.data.entity.mojang.EntityGeometryMojangData; import org.useless.dragonfly.models.entity.StaticEntityModel; +import org.useless.util.AnimationHelper; import turniplabs.halplibe.util.GameStartEntrypoint; import java.util.Objects; @@ -29,7 +31,7 @@ public class DragonFly implements GameStartEntrypoint { @Override public void beforeGameStart() { - + //new ModEntity().initializeEntities(); } @@ -53,11 +55,17 @@ public static Random getRandomFromPos(int x, int y, int z){ return Objects.requireNonNull(BlockModelMojangData.Cache.loadModelData(mc.texturePackList, id), "Cannot find model for id '" + id + "'!"); } + public static @NotNull StaticEntityModel loadEntityModel(@NotNull final String id, final double inflation) { final Minecraft mc = Minecraft.getMinecraft(); return Objects.requireNonNull(EntityGeometryMojangData.Cache.getModel(id, inflation), "Cannot find model for id '" + id + "'!"); } + public static @NotNull Animation loadEntityAnimations(@NotNull final String modid, @NotNull final String id) { + final Minecraft mc = Minecraft.getMinecraft(); + return Objects.requireNonNull(AnimationHelper.getOrCreateEntityAnimation(modid, id), "Cannot find model for id '" + id + "'!"); + } + public static @NotNull BlockstateData loadStateData(@NotNull final String id) { final Minecraft mc = Minecraft.getMinecraft(); BlockstateData blockstateData = Objects.requireNonNull(BlockstateData.Cache.loadStateData(mc.texturePackList, id), "Cannot find model for id '" + id + "'!"); diff --git a/src/main/java/org/useless/DragonFlyClient.java b/src/main/java/org/useless/DragonFlyClient.java new file mode 100644 index 0000000..01647a5 --- /dev/null +++ b/src/main/java/org/useless/DragonFlyClient.java @@ -0,0 +1,38 @@ +package org.useless; + +import net.minecraft.client.render.EntityRenderDispatcher; +import net.minecraft.client.render.TileEntityRenderDispatcher; +import net.minecraft.client.render.block.color.BlockColorDispatcher; +import net.minecraft.client.render.block.model.BlockModelDispatcher; +import net.minecraft.client.render.item.model.ItemModelDispatcher; +import turniplabs.halplibe.util.ModelEntrypoint; + +public class DragonFlyClient implements ModelEntrypoint { + //public static final @NotNull Animation animation = DragonFly.loadEntityAnimations("dragonfly", "walk"); + + + @Override + public void initEntityModels(EntityRenderDispatcher dispatcher) { + //DragonFly.loadEntityModel("geometry.dragonfly.test", 0); + //ModelHelper.setEntityModel(TestMob.class, () -> new MobRendererTest()); + } + + @Override + public void initTileEntityModels(TileEntityRenderDispatcher tileEntityRenderDispatcher) { + + } + + @Override + public void initBlockColors(BlockColorDispatcher blockColorDispatcher) { + + } + + @Override + public void initBlockModels(BlockModelDispatcher blockModelDispatcher) { + + } + + @Override + public void initItemModels(ItemModelDispatcher itemModelDispatcher) { + } +} diff --git a/src/main/java/org/useless/dragonfly/animation/Animation.java b/src/main/java/org/useless/dragonfly/animation/Animation.java new file mode 100644 index 0000000..93f1cd9 --- /dev/null +++ b/src/main/java/org/useless/dragonfly/animation/Animation.java @@ -0,0 +1,29 @@ +package org.useless.dragonfly.animation; + +import java.util.List; + +public class Animation { + private final List animations; + private final float animation_length; + private final boolean loop; + + + public List getAnimations() { + return animations; + } + + + public float getAnimationLength() { + return animation_length; + } + + public boolean isLoop() { + return loop; + } + + public Animation(List animations, float animationLength, boolean loop) { + this.animations = animations; + animation_length = animationLength; + this.loop = loop; + } +} diff --git a/src/main/java/org/useless/dragonfly/animation/AnimationData.java b/src/main/java/org/useless/dragonfly/animation/AnimationData.java new file mode 100644 index 0000000..b766ad2 --- /dev/null +++ b/src/main/java/org/useless/dragonfly/animation/AnimationData.java @@ -0,0 +1,29 @@ +package org.useless.dragonfly.animation; + +import java.util.List; + +public class AnimationData { + + private final String bone; + private final String target; + private final List keyFrames; + + public AnimationData(String bone, String target, List keyFrames) { + + this.bone = bone; + this.target = target; + this.keyFrames = keyFrames; + } + + public String getTarget() { + return target; + } + + public String getBone() { + return bone; + } + + public List getKeyFrames() { + return keyFrames; + } +} diff --git a/src/main/java/org/useless/dragonfly/animation/KeyFrameData.java b/src/main/java/org/useless/dragonfly/animation/KeyFrameData.java new file mode 100644 index 0000000..1bd3289 --- /dev/null +++ b/src/main/java/org/useless/dragonfly/animation/KeyFrameData.java @@ -0,0 +1,27 @@ +package org.useless.dragonfly.animation; + +import java.util.List; + +public class KeyFrameData { + private final List target; + private final float timestamp; + private final String interpolation; + + public KeyFrameData(List target, float timestamp, String interpolation) { + this.target = target; + this.timestamp = timestamp; + this.interpolation = interpolation; + } + + public List getTarget() { + return target; + } + + public String getInterpolation() { + return interpolation; + } + + public float getTimestamp() { + return timestamp; + } +} diff --git a/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java b/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java index 824ea32..8cbda06 100644 --- a/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java +++ b/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java @@ -1,23 +1,13 @@ package org.useless.dragonfly.data.entity.mojang; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonParser; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; +import com.google.gson.*; import com.mojang.logging.LogUtils; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.render.texturepack.TexturePack; import net.minecraft.client.render.texturepack.TexturePackList; import net.minecraft.core.util.HardIllegalArgumentException; import net.minecraft.core.util.collection.NamespaceID; -import net.minecraft.core.util.collection.Pair; +import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -30,13 +20,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; public class EntityGeometryMojangData implements EntityModelData { public static final @NotNull String CURRENT_VERSION = "1.8.0"; diff --git a/src/main/java/org/useless/dragonfly/data/entity/mojang/MojangAnimationDeserializer.java b/src/main/java/org/useless/dragonfly/data/entity/mojang/MojangAnimationDeserializer.java new file mode 100644 index 0000000..bc7bc0a --- /dev/null +++ b/src/main/java/org/useless/dragonfly/data/entity/mojang/MojangAnimationDeserializer.java @@ -0,0 +1,74 @@ +package org.useless.dragonfly.data.entity.mojang; + +import com.google.gson.*; +import org.spongepowered.include.com.google.common.collect.Lists; +import org.useless.dragonfly.animation.Animation; +import org.useless.dragonfly.animation.AnimationData; +import org.useless.dragonfly.animation.KeyFrameData; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class MojangAnimationDeserializer implements JsonDeserializer { + + @Override + public Animation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + List animations = new ArrayList<>(); + float length = 0; + boolean loop = false; + if (json.isJsonObject()) { + JsonObject obj = (JsonObject) json; + if (obj.has("animations")) { + for (JsonElement entry : obj.getAsJsonArray("animations")) { + animations.add(makeAnimation((JsonObject) entry)); + } + } + if (obj.has("length")) { + length = obj.getAsJsonPrimitive("length").getAsFloat(); + } + if (obj.has("loop")) { + loop = obj.getAsJsonPrimitive("loop").getAsBoolean(); + } + return new Animation(animations, length, loop); + } + return new Animation(animations, 0, true); + } + + + private AnimationData makeAnimation(JsonObject object) { + String bone = object.has("bone") ? object.getAsJsonPrimitive("bone").getAsString() : ""; + + String target = object.has("target") ? object.getAsJsonPrimitive("target").getAsString() : "linear"; + List boneMap = object.has("keyframes") ? makeKeyFrames(object.getAsJsonArray("keyframes")) : new ArrayList<>(); + + return new AnimationData(bone, target, boneMap); + } + + private List makeKeyFrames(JsonArray object) { + List keyFrames = new ArrayList<>(); + for (JsonElement entry : object.asList()) { + keyFrames.add(makePost(entry.getAsJsonObject())); + } + return keyFrames; + } + + + private KeyFrameData makePost(JsonElement element) { + List list = Lists.newArrayList(); + + if (element instanceof JsonObject) { + JsonObject jsonObject = (JsonObject) element; + list.add(0, jsonObject.getAsJsonArray("target").get(0).getAsFloat()); + list.add(1, jsonObject.getAsJsonArray("target").get(1).getAsFloat()); + list.add(2, jsonObject.getAsJsonArray("target").get(2).getAsFloat()); + + float timestamp = jsonObject.has("timestamp") ? jsonObject.getAsJsonPrimitive("timestamp").getAsFloat() : 0; + String interpolation = jsonObject.has("interpolation") ? jsonObject.getAsJsonPrimitive("interpolation").getAsString() : "linear"; + + + return new KeyFrameData(list, timestamp, interpolation); + } + return null; + } +} diff --git a/src/main/java/org/useless/dragonfly/renderer/MobRenderer.java b/src/main/java/org/useless/dragonfly/renderer/MobRenderer.java index 269ba25..2029eb7 100644 --- a/src/main/java/org/useless/dragonfly/renderer/MobRenderer.java +++ b/src/main/java/org/useless/dragonfly/renderer/MobRenderer.java @@ -12,15 +12,21 @@ import net.minecraft.core.util.phys.AABB; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; import org.slf4j.Logger; +import org.useless.dragonfly.animation.Animation; import org.useless.dragonfly.models.entity.StaticEntityModel; +import org.useless.dragonfly.models.entity.mojang.StaticEntityModelMojang; +import org.useless.util.AnimationHelper; +import org.useless.util.AnimationState; import java.util.ArrayList; import java.util.List; public abstract class MobRenderer extends EntityRenderer { + public Vector3f VEC_ANIMATION = new Vector3f(); public static final float ENTITY_RENDER_SCALE = 16f; private static final Logger LOGGER = LogUtils.getLogger(); private final @NotNull List<@Nullable StaticEntityModel> setupModels = new ArrayList<>(); @@ -230,4 +236,22 @@ public void loadEntityTexture(final @NotNull T entity) // return this.entityModel.visibleBounds().cloneMove(entity.x, entity.y, entity.z); return super.entityViewBox(entity); } + + /* + * Animation Util + */ + protected void animateWalk(StaticEntityModelMojang staticEntityModelMojang, Animation animationData, float p_268057_, float p_268347_, float p_268138_, float p_268165_) { + long time = (long) (p_268057_ * 50.0F * p_268138_); + float scale = Math.min(p_268347_ * p_268165_, 1.0F); + AnimationHelper.animate(staticEntityModelMojang, animationData, time, scale, VEC_ANIMATION); + } + + protected void applyStatic(StaticEntityModelMojang staticEntityModelMojang, Animation animationData) { + AnimationHelper.animate(staticEntityModelMojang, animationData, 0L, 1.0F, VEC_ANIMATION); + } + + protected void animate(StaticEntityModelMojang staticEntityModelMojang, AnimationState animationState, Animation animationData, float p_233388_, float p_233389_) { + animationState.updateTime(p_233388_, p_233389_); + animationState.ifStarted(p_233392_ -> AnimationHelper.animate(staticEntityModelMojang, animationData, p_233392_.getAccumulatedTime(), 1.0F, VEC_ANIMATION)); + } } diff --git a/src/main/java/org/useless/test_entity/MobRendererTest.java b/src/main/java/org/useless/test_entity/MobRendererTest.java new file mode 100644 index 0000000..8643420 --- /dev/null +++ b/src/main/java/org/useless/test_entity/MobRendererTest.java @@ -0,0 +1,27 @@ +package org.useless.test_entity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.useless.DragonFly; +import org.useless.dragonfly.models.entity.StaticEntityModel; +import org.useless.dragonfly.models.entity.mojang.StaticEntityModelMojang; +import org.useless.dragonfly.renderer.MobRenderer; + +public class MobRendererTest extends MobRenderer { + public MobRendererTest() { + super(0.5F); + } + + + @Override + protected @Nullable StaticEntityModel getAndSetupModelForLayer(@NotNull TestMob entity, float brightness, float partialTick, int layer) { + StaticEntityModel staticEntityModel = DragonFly.loadEntityModel("geometry.dragonfly.test", 0); + if (staticEntityModel instanceof StaticEntityModelMojang) { + staticEntityModel.resetBones(); + + + //animateWalk((StaticEntityModelMojang)staticEntityModel, DragonFlyClient.animation, getLimbSwing(entity, partialTick), getLimbYaw(entity, partialTick), 2.0F, 2.5F); + } + return staticEntityModel; + } +} diff --git a/src/main/java/org/useless/test_entity/ModEntity.java b/src/main/java/org/useless/test_entity/ModEntity.java new file mode 100644 index 0000000..835c9bf --- /dev/null +++ b/src/main/java/org/useless/test_entity/ModEntity.java @@ -0,0 +1,17 @@ +package org.useless.test_entity; + +import net.minecraft.core.util.collection.NamespaceID; +import turniplabs.halplibe.helper.EntityHelper; + +import static org.useless.DragonFly.MOD_ID; + +public class ModEntity { + + public String entityKey(String string) { + return MOD_ID + ".entity." + string; + } + + public void initializeEntities() { + EntityHelper.createEntity(TestMob.class, NamespaceID.getPermanent(MOD_ID, "test"), entityKey("test")); + } +} diff --git a/src/main/java/org/useless/test_entity/TestMob.java b/src/main/java/org/useless/test_entity/TestMob.java new file mode 100644 index 0000000..86c1d86 --- /dev/null +++ b/src/main/java/org/useless/test_entity/TestMob.java @@ -0,0 +1,11 @@ +package org.useless.test_entity; + +import net.minecraft.core.entity.monster.MobMonster; +import net.minecraft.core.world.World; +import org.jetbrains.annotations.Nullable; + +public class TestMob extends MobMonster { + public TestMob(@Nullable World world) { + super(world); + } +} diff --git a/src/main/java/org/useless/util/AnimationHelper.java b/src/main/java/org/useless/util/AnimationHelper.java new file mode 100644 index 0000000..dc832f1 --- /dev/null +++ b/src/main/java/org/useless/util/AnimationHelper.java @@ -0,0 +1,260 @@ +package org.useless.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonReader; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.core.data.DataLoader; +import net.minecraft.core.util.helper.MathHelper; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3f; +import org.useless.DragonFly; +import org.useless.dragonfly.animation.Animation; +import org.useless.dragonfly.animation.AnimationData; +import org.useless.dragonfly.animation.KeyFrameData; +import org.useless.dragonfly.data.entity.mojang.Bone; +import org.useless.dragonfly.data.entity.mojang.MojangAnimationDeserializer; +import org.useless.dragonfly.models.entity.BoneTransform; +import org.useless.dragonfly.models.entity.mojang.StaticEntityModelMojang; + +import javax.swing.text.Utilities; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; +import java.util.function.IntPredicate; +import java.util.stream.Collectors; + +public class AnimationHelper { + public static final Map registeredAnimations = new HashMap<>(); + + private static final double DEGREES_TO_RADIANS = 0.017453292519943295; + + + protected final static Gson gson = builder().create(); + + protected static GsonBuilder builder() { + final GsonBuilder builder = new GsonBuilder(); + builder.setPrettyPrinting(); + builder.registerTypeAdapter(Animation.class, new MojangAnimationDeserializer()); + return builder; + } + + public static InputStream getResourceAsStream(String path) { + try { + return Objects.requireNonNull(DataLoader.class.getResourceAsStream(path)); + } catch (Exception ignored) { + } + try { + return Objects.requireNonNull(Utilities.class.getResourceAsStream(path)); + } catch (Exception ignored) { + } + try { + return Objects.requireNonNull(DragonFly.class.getResourceAsStream(path)); + } catch (Exception ignored) { + } + try { + return Objects.requireNonNull(FabricLoader.getInstance().getClass().getResourceAsStream(path)); + } catch (Exception ignored) { + } + try { + return Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResourceAsStream(path)); + } catch (Exception ignored) { + } + throw new RuntimeException("Resource at '" + path + "' returned null! Does this file exist?"); + } + + public static Animation getOrCreateEntityAnimation(String modID, String animationSource) { + String animationKey = getAnimationLocation(modID, animationSource); + if (registeredAnimations.containsKey(animationKey)) { + return registeredAnimations.get(animationKey); + } + + JsonReader reader = new JsonReader(new BufferedReader(new InputStreamReader(getResourceAsStream(animationKey)))); + Animation animation = gson.fromJson(reader, Animation.class); + registeredAnimations.put(animationKey, animation); + return animation; + } + + public static String getAnimationLocation(String modID, String animationSource) { + if (!animationSource.endsWith(".json")) { + animationSource += ".json"; + } + return "/assets/" + modID + "/animation/" + animationSource; + } + + public static void animate(StaticEntityModelMojang entityModel, Animation animationData, long time, float scale, Vector3f p_253861_) { + float seconds = getElapsedSeconds(animationData, time); + + for (AnimationData animationData1 : animationData.getAnimations()) { + for (@NotNull Bone bone : entityModel.bones) { + if (animationData1.getBone().equals(bone.name) && animationData1.getKeyFrames() != null) { + List positionFrame = animationData1.getKeyFrames().stream().sorted(Comparator.comparing(KeyFrameData::getTimestamp)).collect(Collectors.toList()); + + int i = Math.max(0, binarySearch(0, positionFrame.size(), p_232315_ -> seconds <= positionFrame.get(p_232315_).getTimestamp()) - 1); + int j = Math.min(positionFrame.size() - 1, i + 1); + KeyFrameData keyframe = positionFrame.get(i); + KeyFrameData keyframe1 = positionFrame.get(j); + float f1 = seconds - keyframe.getTimestamp(); + float f2; + if (j != i) { + f2 = MathHelper.clamp(f1 / (keyframe1.getTimestamp() - keyframe.getTimestamp()), 0.0F, 1.0F); + } else { + f2 = 0.0F; + } + + switch (animationData1.getTarget()) { + case "position": + if (keyframe1.getInterpolation().equals("catmullrom")) { + Vector3f vector3f = posVec(convert(positionFrame.get(Math.max(0, i - 1)).getTarget())); + Vector3f vector3f1 = posVec(convert(positionFrame.get(i).getTarget())); + Vector3f vector3f2 = posVec(convert(positionFrame.get(j).getTarget())); + Vector3f vector3f3 = posVec(convert(positionFrame.get(Math.min(positionFrame.size() - 1, j + 1)).getTarget())); + + p_253861_.set( + catmullrom(f2, vector3f.x, vector3f1.x, vector3f2.x, vector3f3.x) * scale, + catmullrom(f2, vector3f.y, vector3f1.y, vector3f2.y, vector3f3.y) * scale, + catmullrom(f2, vector3f.z, vector3f1.z, vector3f2.z, vector3f3.z) * scale + ); + BoneTransform boneTransform = entityModel.getTransform(animationData1.getBone()); + boneTransform.posX += p_253861_.x; + boneTransform.posY += p_253861_.y; + boneTransform.posZ += p_253861_.z; + } else { + Vector3f vector3f = posVec(convert(positionFrame.get(i).getTarget())); + Vector3f vector3f1 = posVec(convert(positionFrame.get(i).getTarget())); + p_253861_.set( + fma(vector3f1.x - vector3f.x, f2, vector3f.x) * scale, + fma(vector3f1.y - vector3f.y, f2, vector3f.y) * scale, + fma(vector3f1.z - vector3f.z, f2, vector3f.z) * scale + ); + BoneTransform boneTransform = entityModel.getTransform(animationData1.getBone()); + boneTransform.posX += p_253861_.x; + boneTransform.posY += p_253861_.y; + boneTransform.posZ += p_253861_.z; + } + break; + case "rotation": + if (keyframe1.getInterpolation().equals("catmullrom")) { + Vector3f vector3f = degreeVec(convert(positionFrame.get(Math.max(0, i - 1)).getTarget())); + Vector3f vector3f1 = degreeVec(convert(positionFrame.get(i).getTarget())); + Vector3f vector3f2 = degreeVec(convert(positionFrame.get(j).getTarget())); + Vector3f vector3f3 = degreeVec(convert(positionFrame.get(Math.min(positionFrame.size() - 1, j + 1)).getTarget())); + + p_253861_.set( + catmullrom(f2, vector3f.x, vector3f1.x, vector3f2.x, vector3f3.x) * scale, + catmullrom(f2, vector3f.y, vector3f1.y, vector3f2.y, vector3f3.y) * scale, + catmullrom(f2, vector3f.z, vector3f1.z, vector3f2.z, vector3f3.z) * scale + ); + BoneTransform boneTransform = entityModel.getTransform(animationData1.getBone()); + boneTransform.rotX += p_253861_.x; + boneTransform.rotY += p_253861_.y; + boneTransform.rotZ += p_253861_.z; + } else { + Vector3f vector3f = degreeVec(convert(positionFrame.get(i).getTarget())); + Vector3f vector3f1 = degreeVec(convert(positionFrame.get(i).getTarget())); + p_253861_.set( + fma(vector3f1.x - vector3f.x, f2, vector3f.x) * scale, + fma(vector3f1.y - vector3f.y, f2, vector3f.y) * scale, + fma(vector3f1.z - vector3f.z, f2, vector3f.z) * scale + ); + BoneTransform boneTransform = entityModel.getTransform(animationData1.getBone()); + boneTransform.rotX += p_253861_.x; + boneTransform.rotY += p_253861_.y; + boneTransform.rotZ += p_253861_.z; + } + break; + case "scale": + if (keyframe1.getInterpolation().equals("catmullrom")) { + Vector3f vector3f = scaleVec(convert(positionFrame.get(Math.max(0, i - 1)).getTarget())); + Vector3f vector3f1 = scaleVec(convert(positionFrame.get(i).getTarget())); + Vector3f vector3f2 = scaleVec(convert(positionFrame.get(j).getTarget())); + Vector3f vector3f3 = scaleVec(convert(positionFrame.get(Math.min(positionFrame.size() - 1, j + 1)).getTarget())); + + p_253861_.set( + catmullrom(f2, vector3f.x, vector3f1.x, vector3f2.x, vector3f3.x) * scale, + catmullrom(f2, vector3f.y, vector3f1.y, vector3f2.y, vector3f3.y) * scale, + catmullrom(f2, vector3f.z, vector3f1.z, vector3f2.z, vector3f3.z) * scale + ); + BoneTransform boneTransform = entityModel.getTransform(animationData1.getBone()); + boneTransform.scaleX += p_253861_.x; + boneTransform.scaleY += p_253861_.y; + boneTransform.scaleZ += p_253861_.z; + } else { + Vector3f vector3f = scaleVec(convert(positionFrame.get(i).getTarget())); + Vector3f vector3f1 = scaleVec(convert(positionFrame.get(i).getTarget())); + p_253861_.set( + fma(vector3f1.x - vector3f.x, f2, vector3f.x) * scale, + fma(vector3f1.y - vector3f.y, f2, vector3f.y) * scale, + fma(vector3f1.z - vector3f.z, f2, vector3f.z) * scale + ); + BoneTransform boneTransform = entityModel.getTransform(animationData1.getBone()); + boneTransform.scaleX += p_253861_.x; + boneTransform.scaleY += p_253861_.y; + boneTransform.scaleZ += p_253861_.z; + } + break; + } + } + } + } + } + + public static float fma(float a, float b, float c) { + return a * b + c; + } + + public static float catmullrom(float p_216245_, float p_216246_, float p_216247_, float p_216248_, float p_216249_) { + return 0.5F * (2.0F * p_216247_ + (p_216248_ - p_216246_) * p_216245_ + (2.0F * p_216246_ - 5.0F * p_216247_ + 4.0F * p_216248_ - p_216249_) * p_216245_ * p_216245_ + (3.0F * p_216247_ - p_216246_ - 3.0F * p_216248_ + p_216249_) * p_216245_ * p_216245_ * p_216245_); + } + + private static int binarySearch(int startIndex, int endIndex, IntPredicate p_14052_) { + int searchSize = endIndex - startIndex; + + while (searchSize > 0) { + int j = searchSize / 2; + int k = startIndex + j; + if (p_14052_.test(k)) { + searchSize = j; + } else { + startIndex = k + 1; + searchSize -= j + 1; + } + } + + return startIndex; + } + + public static Vector3f convert(List floats) { + return new Vector3f(floats.get(0), floats.get(1), floats.get(2)); + } + + public static Vector3f posVec(Vector3f vector3f) { + return new Vector3f(vector3f.x, vector3f.y, vector3f.z); + } + + public static Vector3f posVec(float p_253691_, float p_254046_, float p_254461_) { + return new Vector3f(p_253691_, p_254046_, p_254461_); + } + + public static Vector3f degreeVec(float p_254402_, float p_253917_, float p_254397_) { + return new Vector3f(p_254402_ * ((float) Math.PI / 180F), p_253917_ * ((float) Math.PI / 180F), p_254397_ * ((float) Math.PI / 180F)); + } + + public static Vector3f degreeVec(Vector3f vector3f) { + return new Vector3f(vector3f.x * ((float) Math.PI / 180F), vector3f.y * ((float) Math.PI / 180F), vector3f.z * ((float) Math.PI / 180F)); + } + + public static Vector3f scaleVec(double p_253806_, double p_253647_, double p_254396_) { + return new Vector3f((float) (p_253806_ - (double) 1.0F), (float) (p_253647_ - (double) 1.0F), (float) (p_254396_ - (double) 1.0F)); + } + + public static Vector3f scaleVec(Vector3f vector3f) { + return new Vector3f((float) (vector3f.x - (double) 1.0F), (float) (vector3f.y - (double) 1.0F), (float) (vector3f.z - (double) 1.0F)); + } + + private static float getElapsedSeconds(Animation animationData, long ms) { + float seconds = (float) ms / 1000.0F; + return animationData.isLoop() ? seconds % animationData.getAnimationLength() : seconds; + } +} diff --git a/src/main/java/org/useless/util/AnimationState.java b/src/main/java/org/useless/util/AnimationState.java new file mode 100644 index 0000000..f85ab62 --- /dev/null +++ b/src/main/java/org/useless/util/AnimationState.java @@ -0,0 +1,58 @@ +package org.useless.util; + +import java.util.function.Consumer; + +public class AnimationState { + private long lastTime = Long.MAX_VALUE; + private long accumulatedTime; + + public void start(int p_216978_) { + this.lastTime = (long) p_216978_ * 1000L / 20L; + this.accumulatedTime = 0L; + } + + public void startIfStopped(int p_216983_) { + if (!this.isStarted()) { + this.start(p_216983_); + } + } + + public void animateWhen(boolean p_252220_, int p_249486_) { + if (p_252220_) { + this.startIfStopped(p_249486_); + } else { + this.stop(); + } + } + + public void stop() { + this.lastTime = Long.MAX_VALUE; + } + + public void ifStarted(Consumer p_216980_) { + if (this.isStarted()) { + p_216980_.accept(this); + } + } + + public void updateTime(float p_216975_, float p_216976_) { + if (this.isStarted()) { + long i = lfloor(p_216975_ * 1000.0F / 20.0F); + this.accumulatedTime += (long) ((float) (i - this.lastTime) * p_216976_); + this.lastTime = i; + } + } + + private static long lfloor(double p_14135_) { + long i = (long) p_14135_; + return p_14135_ < (double) i ? i - 1L : i; + } + + public long getAccumulatedTime() { + return this.accumulatedTime; + } + + public boolean isStarted() { + return this.lastTime != Long.MAX_VALUE; + } +} diff --git a/src/main/resources/assets/dragonfly/animation/walk.json b/src/main/resources/assets/dragonfly/animation/walk.json new file mode 100644 index 0000000..ac4933e --- /dev/null +++ b/src/main/resources/assets/dragonfly/animation/walk.json @@ -0,0 +1,222 @@ +{ + "length": 1, + "animations": [ + { + "bone": "RightArm", + "target": "rotation", + "keyframes": [ + { + "timestamp": 0, + "target": [ + 0, + 0, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 0.5, + "target": [ + 22.5, + 0, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 1, + "target": [ + 0, + 0, + 0 + ], + "interpolation": "catmullrom" + } + ] + }, + { + "bone": "LeftArm", + "target": "rotation", + "keyframes": [ + { + "timestamp": 0, + "target": [ + 0, + 0, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 0.5, + "target": [ + 22.5, + 0, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 1, + "target": [ + 0, + 0, + 0 + ], + "interpolation": "catmullrom" + } + ] + }, + { + "bone": "RightLeg", + "target": "position", + "keyframes": [ + { + "timestamp": 0, + "target": [ + 0, + 0, + -1 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 0.5, + "target": [ + 0, + 0, + 1 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 0.75, + "target": [ + 0, + 3, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 1, + "target": [ + 0, + 0, + -1 + ], + "interpolation": "catmullrom" + } + ] + }, + { + "bone": "RightLeg", + "target": "rotation", + "keyframes": [ + { + "timestamp": 0, + "target": [ + -17.5, + 0, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 0.5, + "target": [ + 17.5, + 0, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 1, + "target": [ + -17.5, + 0, + 0 + ], + "interpolation": "catmullrom" + } + ] + }, + { + "bone": "LeftLeg", + "target": "position", + "keyframes": [ + { + "timestamp": 0, + "target": [ + 0, + 0, + 1 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 0.25, + "target": [ + 0, + 3, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 0.5, + "target": [ + 0, + 0, + -1 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 1, + "target": [ + 0, + 0, + 1 + ], + "interpolation": "catmullrom" + } + ] + }, + { + "bone": "LeftLeg", + "target": "rotation", + "keyframes": [ + { + "timestamp": 0, + "target": [ + 17.5, + 0, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 0.5, + "target": [ + -17.5, + 0, + 0 + ], + "interpolation": "catmullrom" + }, + { + "timestamp": 1, + "target": [ + 17.5, + 0, + 0 + ], + "interpolation": "catmullrom" + } + ] + } + ], + "loop": true +} diff --git a/src/main/resources/assets/dragonfly/models/entity/model.geo.json b/src/main/resources/assets/dragonfly/models/entity/model.geo.json new file mode 100644 index 0000000..9a831e1 --- /dev/null +++ b/src/main/resources/assets/dragonfly/models/entity/model.geo.json @@ -0,0 +1,279 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.dragonfly.test", + "texture_width": 64, + "texture_height": 64, + "visible_bounds_width": 3, + "visible_bounds_height": 3.5, + "visible_bounds_offset": [ + 0, + 1.25, + 0 + ] + }, + "bones": [ + { + "name": "Head", + "pivot": [ + 0, + 24, + 0 + ], + "cubes": [ + { + "origin": [ + -4, + 24, + -4 + ], + "size": [ + 8, + 8, + 8 + ], + "uv": [ + 0, + 0 + ] + }, + { + "origin": [ + -4, + 24, + -4 + ], + "size": [ + 8, + 8, + 8 + ], + "inflate": 0.5, + "uv": [ + 32, + 0 + ] + } + ] + }, + { + "name": "Body", + "pivot": [ + 0, + 24, + 0 + ], + "cubes": [ + { + "origin": [ + -4, + 12, + -2 + ], + "size": [ + 8, + 12, + 4 + ], + "uv": [ + 16, + 16 + ] + }, + { + "origin": [ + -4, + 12, + -2 + ], + "size": [ + 8, + 12, + 4 + ], + "inflate": 0.25, + "uv": [ + 16, + 32 + ] + } + ] + }, + { + "name": "RightArm", + "pivot": [ + -5, + 22, + 0 + ], + "cubes": [ + { + "origin": [ + -8, + 12, + -2 + ], + "size": [ + 4, + 12, + 4 + ], + "uv": [ + 40, + 16 + ] + }, + { + "origin": [ + -8, + 12, + -2 + ], + "size": [ + 4, + 12, + 4 + ], + "inflate": 0.25, + "uv": [ + 40, + 32 + ] + } + ] + }, + { + "name": "LeftArm", + "pivot": [ + 5, + 22, + 0 + ], + "cubes": [ + { + "origin": [ + 4, + 12, + -2 + ], + "size": [ + 4, + 12, + 4 + ], + "uv": [ + 32, + 48 + ] + }, + { + "origin": [ + 4, + 12, + -2 + ], + "size": [ + 4, + 12, + 4 + ], + "inflate": 0.25, + "uv": [ + 48, + 48 + ] + } + ] + }, + { + "name": "RightLeg", + "pivot": [ + -1.9, + 12, + 0 + ], + "cubes": [ + { + "origin": [ + -3.9, + 0, + -2 + ], + "size": [ + 4, + 12, + 4 + ], + "uv": [ + 0, + 16 + ] + }, + { + "origin": [ + -3.9, + 0, + -2 + ], + "size": [ + 4, + 12, + 4 + ], + "inflate": 0.25, + "uv": [ + 0, + 32 + ] + } + ] + }, + { + "name": "LeftLeg", + "pivot": [ + 1.9, + 12, + 0 + ], + "cubes": [ + { + "origin": [ + -0.1, + 0, + -2 + ], + "size": [ + 4, + 12, + 4 + ], + "uv": [ + 16, + 48 + ] + }, + { + "origin": [ + -0.1, + 0, + -2 + ], + "size": [ + 4, + 12, + 4 + ], + "inflate": 0.25, + "uv": [ + 0, + 48 + ] + } + ] + } + ] + } + ] +} diff --git a/src/main/resources/assets/dragonfly/models/entity/models.json b/src/main/resources/assets/dragonfly/models/entity/models.json new file mode 100644 index 0000000..d41c98a --- /dev/null +++ b/src/main/resources/assets/dragonfly/models/entity/models.json @@ -0,0 +1,5 @@ +{ + "model_paths": [ + "assets/dragonfly/models/entity/model.geo.json" + ] +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index fc47c36..bae5cb2 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,6 +24,9 @@ ], "afterGameStart": [ "org.useless.DragonFly" + ], + "initModels": [ + "org.useless.DragonFlyClient" ] }, "mixins": [