├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ └── java │ └── software │ └── bernie │ └── geckolib3 │ └── core │ ├── util │ ├── Axis.java │ ├── Memoizer.java │ ├── MathUtil.java │ └── Color.java │ ├── PlayState.java │ ├── IAnimationTickable.java │ ├── AnimationState.java │ ├── molang │ ├── MolangException.java │ ├── functions │ │ ├── CosDegrees.java │ │ └── SinDegrees.java │ ├── expressions │ │ ├── MolangAssignment.java │ │ ├── MolangValue.java │ │ ├── MolangMultiStatement.java │ │ └── MolangExpression.java │ ├── LazyVariable.java │ └── MolangParser.java │ ├── IAnimatable.java │ ├── easing │ ├── EasingFunctionArgs.java │ ├── EasingType.java │ └── EasingManager.java │ ├── keyframe │ ├── ParticleEventKeyFrame.java │ ├── EventKeyFrame.java │ ├── BoneAnimation.java │ ├── AnimationPointQueue.java │ ├── KeyFrameLocation.java │ ├── BoneAnimationQueue.java │ ├── AnimationPoint.java │ ├── VectorKeyFrameList.java │ └── KeyFrame.java │ ├── snapshot │ ├── DirtyTracker.java │ └── BoneSnapshot.java │ ├── manager │ ├── InstancedAnimationFactory.java │ ├── SingletonAnimationFactory.java │ ├── AnimationFactory.java │ └── AnimationData.java │ ├── ConstantValue.java │ ├── event │ ├── SoundKeyframeEvent.java │ ├── ParticleKeyFrameEvent.java │ ├── CustomInstructionKeyframeEvent.java │ ├── KeyframeEvent.java │ └── predicate │ │ └── AnimationEvent.java │ ├── builder │ ├── Animation.java │ ├── ILoopType.java │ ├── RawAnimation.java │ └── AnimationBuilder.java │ ├── processor │ ├── IBone.java │ └── AnimationProcessor.java │ ├── IAnimatableModel.java │ └── controller │ └── AnimationController.java ├── gradle.properties ├── .gitattributes ├── .gitignore ├── settings.gradle ├── LICENSE ├── gradlew.bat └── gradlew /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernie-g/geckolib-core/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/util/Axis.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.util; 2 | 3 | public enum Axis { 4 | X, Y, Z 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/PlayState.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core; 2 | 3 | public enum PlayState { 4 | CONTINUE, STOP 5 | } 6 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Publishing 2 | repsyUrl=https://repo.repsy.io/mvn/gandiber/geckolib 3 | version=1.0.23 4 | group=software.bernie.geckolib 5 | archivesBaseName=geckolib-core -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/IAnimationTickable.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core; 2 | 3 | public interface IAnimationTickable { 4 | public void tick(); 5 | 6 | public int tickTimer(); 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/AnimationState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core; 7 | 8 | public enum AnimationState { 9 | Running, Transitioning, Stopped; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | mdmodsrepo 24 | 25 | # Files from Forge MDK 26 | forge*changelog.txt 27 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/MolangException.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang; 2 | 3 | public class MolangException extends Exception 4 | { 5 | private static final long serialVersionUID = 1470247726869768015L; 6 | 7 | public MolangException(String message) 8 | { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user guide at https://docs.gradle.org/4.10.3/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'geckolib-core' 11 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/IAnimatable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | package software.bernie.geckolib3.core; 6 | 7 | import software.bernie.geckolib3.core.manager.AnimationData; 8 | import software.bernie.geckolib3.core.manager.AnimationFactory; 9 | 10 | /** 11 | * This interface must be applied to any object that wants to be animated 12 | */ 13 | public interface IAnimatable { 14 | void registerControllers(AnimationData data); 15 | 16 | AnimationFactory getFactory(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/easing/EasingFunctionArgs.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.easing; 2 | 3 | import java.util.Objects; 4 | 5 | public record EasingFunctionArgs(EasingType easingType, Double arg0) { 6 | @Override 7 | public boolean equals(Object o) { 8 | if (this == o) 9 | return true; 10 | if (o == null || getClass() != o.getClass()) 11 | return false; 12 | EasingFunctionArgs that = (EasingFunctionArgs)o; 13 | return easingType == that.easingType && Objects.equals(arg0, that.arg0); 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/ParticleEventKeyFrame.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.keyframe; 2 | 3 | public class ParticleEventKeyFrame extends EventKeyFrame { 4 | public final String effect; 5 | public final String locator; 6 | public final String script; 7 | 8 | public ParticleEventKeyFrame(Double startTick, String effect, String locator, String script) { 9 | super(startTick, effect + "\n" + locator + "\n" + script); 10 | this.script = script; 11 | this.locator = locator; 12 | this.effect = effect; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/functions/CosDegrees.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang.functions; 2 | 3 | import com.eliotlash.mclib.math.IValue; 4 | import com.eliotlash.mclib.math.functions.Function; 5 | 6 | public class CosDegrees extends Function { 7 | public CosDegrees(IValue[] values, String name) throws Exception { 8 | super(values, name); 9 | } 10 | 11 | @Override 12 | public int getRequiredArguments() { 13 | return 1; 14 | } 15 | 16 | @Override 17 | public double get() { 18 | return Math.cos(this.getArg(0) / 180 * Math.PI); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/functions/SinDegrees.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang.functions; 2 | 3 | import com.eliotlash.mclib.math.IValue; 4 | import com.eliotlash.mclib.math.functions.Function; 5 | 6 | public class SinDegrees extends Function { 7 | public SinDegrees(IValue[] values, String name) throws Exception { 8 | super(values, name); 9 | } 10 | 11 | @Override 12 | public int getRequiredArguments() { 13 | return 1; 14 | } 15 | 16 | @Override 17 | public double get() { 18 | return Math.sin(this.getArg(0) / 180 * Math.PI); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/EventKeyFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.keyframe; 7 | 8 | public class EventKeyFrame { 9 | private final T eventData; 10 | private final double startTick; 11 | 12 | public EventKeyFrame(double startTick, T eventData) { 13 | this.startTick = startTick; 14 | this.eventData = eventData; 15 | } 16 | 17 | public T getEventData() { 18 | return eventData; 19 | } 20 | 21 | public double getStartTick() { 22 | return startTick; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/BoneAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.keyframe; 7 | 8 | import com.eliotlash.mclib.math.IValue; 9 | 10 | public class BoneAnimation { 11 | public final String boneName; 12 | 13 | public VectorKeyFrameList> rotationKeyFrames; 14 | public VectorKeyFrameList> positionKeyFrames; 15 | public VectorKeyFrameList> scaleKeyFrames; 16 | 17 | public BoneAnimation(String boneName) { 18 | this.boneName = boneName; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/AnimationPointQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.keyframe; 7 | 8 | import java.util.LinkedList; 9 | 10 | import software.bernie.geckolib3.core.processor.IBone; 11 | 12 | /** 13 | * An animation point queue holds a queue of Animation Points which are used in 14 | * the AnimatedEntityModel to lerp between values 15 | */ 16 | public class AnimationPointQueue extends LinkedList { 17 | 18 | private static final long serialVersionUID = 5472797438476621193L; 19 | public IBone model; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/util/Memoizer.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.util; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.function.Function; 6 | 7 | public class Memoizer { 8 | private final Map cache = new ConcurrentHashMap<>(); 9 | 10 | private Memoizer() { 11 | } 12 | 13 | private Function doMemoize(final Function function) { 14 | return input -> cache.computeIfAbsent(input, function::apply); 15 | } 16 | 17 | public static Function memoize(final Function function) { 18 | return new Memoizer().doMemoize(function); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/snapshot/DirtyTracker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.snapshot; 7 | 8 | import software.bernie.geckolib3.core.processor.IBone; 9 | 10 | public class DirtyTracker { 11 | public IBone model; 12 | public boolean hasScaleChanged; 13 | public boolean hasPositionChanged; 14 | public boolean hasRotationChanged; 15 | 16 | public DirtyTracker(boolean hasScaleChanged, boolean hasPositionChanged, boolean hasRotationChanged, IBone model) { 17 | this.hasScaleChanged = hasScaleChanged; 18 | this.hasPositionChanged = hasPositionChanged; 19 | this.hasRotationChanged = hasRotationChanged; 20 | this.model = model; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/manager/InstancedAnimationFactory.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.manager; 2 | 3 | import software.bernie.geckolib3.core.IAnimatable; 4 | 5 | /** 6 | * AnimationFactory implementation for instantiated objects such as Entities or BlockEntities. Returns a single {@link AnimationData} instance per factory. 7 | */ 8 | public class InstancedAnimationFactory extends AnimationFactory { 9 | private AnimationData animationData; 10 | 11 | public InstancedAnimationFactory(IAnimatable animatable) { 12 | super(animatable); 13 | } 14 | 15 | @Override 16 | public AnimationData getOrCreateAnimationData(int uniqueID) { 17 | if (this.animationData == null) { 18 | this.animationData = new AnimationData(); 19 | 20 | this.animatable.registerControllers(this.animationData); 21 | } 22 | 23 | return this.animationData; 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/KeyFrameLocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.keyframe; 7 | 8 | /** 9 | * This class stores a location in an animation, and returns the keyframe that 10 | * should be executed. 11 | * 12 | */ 13 | public class KeyFrameLocation { 14 | /** 15 | * The curent frame. 16 | */ 17 | public T currentFrame; 18 | 19 | /** 20 | * This is the combined total time of all the previous keyframes 21 | */ 22 | public double currentTick; 23 | 24 | /** 25 | * Instantiates a new Key frame location. 26 | * 27 | * @param currentFrame the current frame 28 | * @param currentTick the current animation tick 29 | */ 30 | public KeyFrameLocation(T currentFrame, double currentTick) { 31 | this.currentFrame = currentFrame; 32 | this.currentTick = currentTick; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/expressions/MolangAssignment.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang.expressions; 2 | 3 | import com.eliotlash.mclib.math.IValue; 4 | import com.eliotlash.mclib.math.Variable; 5 | 6 | import software.bernie.geckolib3.core.molang.MolangParser; 7 | 8 | public class MolangAssignment extends MolangExpression { 9 | public Variable variable; 10 | public IValue expression; 11 | 12 | public MolangAssignment(MolangParser context, Variable variable, IValue expression) { 13 | super(context); 14 | 15 | this.variable = variable; 16 | this.expression = expression; 17 | } 18 | 19 | @Override 20 | public double get() { 21 | double value = this.expression.get(); 22 | 23 | this.variable.set(value); 24 | 25 | return value; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return this.variable.getName() + " = " + this.expression.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/BoneAnimationQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.keyframe; 7 | 8 | import software.bernie.geckolib3.core.processor.IBone; 9 | 10 | public record BoneAnimationQueue(IBone bone, AnimationPointQueue rotationXQueue, AnimationPointQueue rotationYQueue, 11 | AnimationPointQueue rotationZQueue, AnimationPointQueue positionXQueue, AnimationPointQueue positionYQueue, 12 | AnimationPointQueue positionZQueue, AnimationPointQueue scaleXQueue, AnimationPointQueue scaleYQueue, 13 | AnimationPointQueue scaleZQueue) { 14 | 15 | public BoneAnimationQueue(IBone bone) { 16 | this(bone, new AnimationPointQueue(), new AnimationPointQueue(), new AnimationPointQueue(), 17 | new AnimationPointQueue(), new AnimationPointQueue(), new AnimationPointQueue(), 18 | new AnimationPointQueue(), new AnimationPointQueue(), new AnimationPointQueue()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/ConstantValue.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core; 2 | 3 | import com.eliotlash.mclib.math.IValue; 4 | 5 | public class ConstantValue implements IValue { 6 | private final double value; 7 | 8 | public ConstantValue(double value) { 9 | this.value = value; 10 | } 11 | 12 | @Override 13 | public double get() { 14 | return value; 15 | } 16 | 17 | public static ConstantValue fromDouble(double d) { 18 | return new ConstantValue(d); 19 | } 20 | 21 | public static ConstantValue fromFloat(float d) { 22 | return new ConstantValue(d); 23 | } 24 | 25 | public static ConstantValue parseDouble(String s) { 26 | return new ConstantValue(Double.parseDouble(s)); 27 | } 28 | 29 | public static ConstantValue parseFloat(String s) { 30 | return new ConstantValue(Float.parseFloat(s)); 31 | } 32 | 33 | public static ConstantValue subtract(IValue first, IValue second) { 34 | return ConstantValue.fromDouble(first.get() - second.get()); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/event/SoundKeyframeEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.event; 7 | 8 | import software.bernie.geckolib3.core.controller.AnimationController; 9 | 10 | public class SoundKeyframeEvent extends KeyframeEvent { 11 | public final String sound; 12 | 13 | /** 14 | * This stores all the fields that are needed in the AnimationTestEvent 15 | * 16 | * @param entity the entity 17 | * @param animationTick The amount of ticks that have passed in either the 18 | * current transition or animation, depending on the 19 | * controller's AnimationState. 20 | * @param sound The name of the sound to play 21 | * @param controller the controller 22 | */ 23 | public SoundKeyframeEvent(T entity, double animationTick, String sound, AnimationController controller) { 24 | super(entity, animationTick, controller); 25 | this.sound = sound; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/manager/SingletonAnimationFactory.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.manager; 2 | 3 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 4 | import software.bernie.geckolib3.core.IAnimatable; 5 | 6 | /** 7 | * AnimationFactory implementation for singleton/flyweight objects such as Items. Utilises a keyed map to differentiate different instances of the object. 8 | */ 9 | public class SingletonAnimationFactory extends AnimationFactory { 10 | private final Int2ObjectOpenHashMap animationDataMap = new Int2ObjectOpenHashMap<>(); 11 | 12 | public SingletonAnimationFactory(IAnimatable animatable) { 13 | super(animatable); 14 | } 15 | 16 | @Override 17 | public AnimationData getOrCreateAnimationData(int uniqueID) { 18 | if (!this.animationDataMap.containsKey(uniqueID)) { 19 | AnimationData data = new AnimationData(); 20 | 21 | this.animatable.registerControllers(data); 22 | this.animationDataMap.put(uniqueID, data); 23 | } 24 | 25 | return this.animationDataMap.get(uniqueID); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/event/ParticleKeyFrameEvent.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.event; 2 | 3 | import software.bernie.geckolib3.core.controller.AnimationController; 4 | 5 | public class ParticleKeyFrameEvent extends KeyframeEvent { 6 | public final String effect; 7 | public final String locator; 8 | public final String script; 9 | 10 | /** 11 | * This stores all the fields that are needed in the AnimationTestEvent 12 | * 13 | * @param entity the entity 14 | * @param animationTick The amount of ticks that have passed in either the 15 | * current transition or animation, depending on the 16 | * controller's AnimationState. 17 | * @param controller the controller 18 | */ 19 | public ParticleKeyFrameEvent(T entity, double animationTick, String effect, String locator, String script, 20 | AnimationController controller) { 21 | super(entity, animationTick, controller); 22 | this.effect = effect; 23 | this.locator = locator; 24 | this.script = script; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/builder/Animation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.builder; 7 | 8 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 9 | import software.bernie.geckolib3.core.builder.ILoopType.EDefaultLoopTypes; 10 | import software.bernie.geckolib3.core.keyframe.BoneAnimation; 11 | import software.bernie.geckolib3.core.keyframe.EventKeyFrame; 12 | import software.bernie.geckolib3.core.keyframe.ParticleEventKeyFrame; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * A specific animation instance 18 | */ 19 | public class Animation { 20 | public String animationName; 21 | public double animationLength = -1; 22 | public ILoopType loop = EDefaultLoopTypes.LOOP; 23 | public List boneAnimations; 24 | public List> soundKeyFrames = new ObjectArrayList<>(); 25 | public List particleKeyFrames = new ObjectArrayList<>(); 26 | public List> customInstructionKeyframes = new ObjectArrayList<>(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 GeckoThePecko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/event/CustomInstructionKeyframeEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.event; 7 | 8 | import software.bernie.geckolib3.core.controller.AnimationController; 9 | 10 | public class CustomInstructionKeyframeEvent extends KeyframeEvent 11 | { 12 | public final String instructions; 13 | 14 | /** 15 | * This stores all the fields that are needed in the AnimationTestEvent 16 | * 17 | * @param entity the entity 18 | * @param animationTick The amount of ticks that have passed in either the current transition or animation, depending on the controller's AnimationState. 19 | * @param instructions A list of all the custom instructions. In blockbench, each line in the custom instruction box is a separate instruction. 20 | * @param controller the controller 21 | */ 22 | public CustomInstructionKeyframeEvent(T entity, double animationTick, String instructions, AnimationController controller) 23 | { 24 | super(entity, animationTick, controller); 25 | this.instructions = instructions; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/expressions/MolangValue.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang.expressions; 2 | 3 | import com.eliotlash.mclib.math.Constant; 4 | import com.eliotlash.mclib.math.IValue; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonPrimitive; 7 | 8 | import software.bernie.geckolib3.core.molang.MolangParser; 9 | 10 | public class MolangValue extends MolangExpression { 11 | public IValue value; 12 | public boolean returns; 13 | 14 | public MolangValue(MolangParser context, IValue value) { 15 | super(context); 16 | 17 | this.value = value; 18 | } 19 | 20 | public MolangExpression addReturn() { 21 | this.returns = true; 22 | 23 | return this; 24 | } 25 | 26 | @Override 27 | public double get() { 28 | return this.value.get(); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return (this.returns ? MolangParser.RETURN : "") + this.value.toString(); 34 | } 35 | 36 | @Override 37 | public JsonElement toJson() { 38 | if (this.value instanceof Constant) { 39 | return new JsonPrimitive(this.value.get()); 40 | } 41 | 42 | return super.toJson(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/LazyVariable.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang; 2 | 3 | import com.eliotlash.mclib.math.Variable; 4 | 5 | import java.util.function.DoubleSupplier; 6 | 7 | /** 8 | * Lazy override of Variable, to allow for deferred value calculation.
9 | * Optimises rendering as values are not touched until needed (if at all) 10 | */ 11 | public class LazyVariable extends Variable { 12 | private DoubleSupplier valueSupplier; 13 | 14 | public LazyVariable(String name, double value) { 15 | this(name, () -> value); 16 | } 17 | 18 | public LazyVariable(String name, DoubleSupplier valueSupplier) { 19 | super(name, 0); 20 | 21 | this.valueSupplier = valueSupplier; 22 | } 23 | 24 | @Override 25 | public void set(double value) { 26 | this.valueSupplier = () -> value; 27 | } 28 | 29 | public void set(DoubleSupplier valueSupplier) { 30 | this.valueSupplier = valueSupplier; 31 | } 32 | 33 | @Override 34 | public double get() { 35 | return this.valueSupplier.getAsDouble(); 36 | } 37 | 38 | public static LazyVariable from(Variable variable) { 39 | return new LazyVariable(variable.getName(), variable.get()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/expressions/MolangMultiStatement.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang.expressions; 2 | 3 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 4 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 5 | import software.bernie.geckolib3.core.molang.LazyVariable; 6 | import software.bernie.geckolib3.core.molang.MolangParser; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.StringJoiner; 11 | 12 | public class MolangMultiStatement extends MolangExpression { 13 | public final List expressions = new ObjectArrayList<>(); 14 | public final Map locals = new Object2ObjectOpenHashMap<>(); 15 | 16 | public MolangMultiStatement(MolangParser context) { 17 | super(context); 18 | } 19 | 20 | @Override 21 | public double get() { 22 | double value = 0; 23 | 24 | for (MolangExpression expression : this.expressions) { 25 | value = expression.get(); 26 | } 27 | 28 | return value; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | StringJoiner builder = new StringJoiner("; "); 34 | 35 | for (MolangExpression expression : this.expressions) { 36 | builder.add(expression.toString()); 37 | 38 | if (expression instanceof MolangValue && ((MolangValue)expression).returns) 39 | break; 40 | } 41 | 42 | return builder.toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/event/KeyframeEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.event; 7 | 8 | import software.bernie.geckolib3.core.controller.AnimationController; 9 | 10 | public abstract class KeyframeEvent { 11 | private final T entity; 12 | private final double animationTick; 13 | private final AnimationController controller; 14 | 15 | /** 16 | * This stores all the fields that are needed in the AnimationTestEvent 17 | * 18 | * @param entity the entity 19 | * @param animationTick The amount of ticks that have passed in either the 20 | * current transition or animation, depending on the 21 | * controller's AnimationState. 22 | * @param controller the controller 23 | */ 24 | public KeyframeEvent(T entity, double animationTick, AnimationController controller) { 25 | this.entity = entity; 26 | this.animationTick = animationTick; 27 | this.controller = controller; 28 | } 29 | 30 | /** 31 | * Gets the amount of ticks that have passed in either the current transition or 32 | * animation, depending on the controller's AnimationState. 33 | * 34 | * @return the animation tick 35 | */ 36 | public double getAnimationTick() { 37 | return animationTick; 38 | } 39 | 40 | public T getEntity() { 41 | return entity; 42 | } 43 | 44 | public AnimationController getController() { 45 | return controller; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/expressions/MolangExpression.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang.expressions; 2 | 3 | import com.eliotlash.mclib.math.Constant; 4 | import com.eliotlash.mclib.math.IValue; 5 | import com.eliotlash.mclib.math.Operation; 6 | import com.google.gson.JsonElement; 7 | import com.google.gson.JsonPrimitive; 8 | 9 | import software.bernie.geckolib3.core.molang.MolangParser; 10 | 11 | public abstract class MolangExpression implements IValue { 12 | public MolangParser context; 13 | 14 | public static boolean isZero(MolangExpression expression) { 15 | return isConstant(expression, 0); 16 | } 17 | 18 | public static boolean isOne(MolangExpression expression) { 19 | return isConstant(expression, 1); 20 | } 21 | 22 | public static boolean isConstant(MolangExpression expression, double x) { 23 | if (expression instanceof MolangValue) { 24 | MolangValue value = (MolangValue) expression; 25 | return value.value instanceof Constant && Operation.equals(value.value.get(), x); 26 | } 27 | 28 | return false; 29 | } 30 | 31 | public static boolean isExpressionConstant(MolangExpression expression) { 32 | if (expression instanceof MolangValue) { 33 | MolangValue value = (MolangValue) expression; 34 | return value.value instanceof Constant; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | public MolangExpression(MolangParser context) { 41 | this.context = context; 42 | } 43 | 44 | public JsonElement toJson() { 45 | return new JsonPrimitive(this.toString()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/builder/ILoopType.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.builder; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonPrimitive; 5 | 6 | import java.util.Locale; 7 | 8 | public interface ILoopType { 9 | 10 | boolean isRepeatingAfterEnd(); 11 | 12 | enum EDefaultLoopTypes implements ILoopType { 13 | LOOP(true), 14 | PLAY_ONCE, 15 | HOLD_ON_LAST_FRAME; 16 | 17 | private final boolean looping; 18 | 19 | EDefaultLoopTypes(boolean looping) { 20 | this.looping = looping; 21 | } 22 | 23 | EDefaultLoopTypes() { 24 | this(false); 25 | } 26 | 27 | @Override 28 | public boolean isRepeatingAfterEnd() { 29 | return this.looping; 30 | } 31 | } 32 | 33 | static ILoopType fromJson(JsonElement json) { 34 | if (json == null || !json.isJsonPrimitive()) { 35 | return EDefaultLoopTypes.PLAY_ONCE; 36 | } 37 | 38 | JsonPrimitive primitive = json.getAsJsonPrimitive(); 39 | 40 | if (primitive.isBoolean()) { 41 | return primitive.getAsBoolean() ? EDefaultLoopTypes.LOOP : EDefaultLoopTypes.PLAY_ONCE; 42 | } 43 | 44 | if (primitive.isString()) { 45 | String string = primitive.getAsString(); 46 | 47 | if (string.equalsIgnoreCase("false")) { 48 | return EDefaultLoopTypes.PLAY_ONCE; 49 | } 50 | 51 | if (string.equalsIgnoreCase("true")) { 52 | return EDefaultLoopTypes.LOOP; 53 | } 54 | 55 | try { 56 | return EDefaultLoopTypes.valueOf(string.toUpperCase(Locale.ROOT)); 57 | } 58 | catch (Exception ex) {} 59 | } 60 | 61 | return EDefaultLoopTypes.PLAY_ONCE; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/processor/IBone.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.processor; 2 | 3 | import software.bernie.geckolib3.core.snapshot.BoneSnapshot; 4 | 5 | public interface IBone { 6 | float getRotationX(); 7 | 8 | float getRotationY(); 9 | 10 | float getRotationZ(); 11 | 12 | float getPositionX(); 13 | 14 | float getPositionY(); 15 | 16 | float getPositionZ(); 17 | 18 | float getScaleX(); 19 | 20 | float getScaleY(); 21 | 22 | float getScaleZ(); 23 | 24 | void setRotationX(float value); 25 | 26 | void setRotationY(float value); 27 | 28 | void setRotationZ(float value); 29 | 30 | void setPositionX(float value); 31 | 32 | void setPositionY(float value); 33 | 34 | void setPositionZ(float value); 35 | 36 | void setScaleX(float value); 37 | 38 | void setScaleY(float value); 39 | 40 | void setScaleZ(float value); 41 | 42 | void setPivotX(float value); 43 | 44 | void setPivotY(float value); 45 | 46 | void setPivotZ(float value); 47 | 48 | float getPivotX(); 49 | 50 | float getPivotY(); 51 | 52 | float getPivotZ(); 53 | 54 | boolean isHidden(); 55 | 56 | boolean cubesAreHidden(); 57 | 58 | boolean childBonesAreHiddenToo(); 59 | 60 | void setHidden(boolean hidden); 61 | 62 | void setCubesHidden(boolean hidden); 63 | 64 | void setHidden(boolean selfHidden, boolean skipChildRendering); 65 | 66 | void setModelRendererName(String modelRendererName); 67 | 68 | void saveInitialSnapshot(); 69 | 70 | BoneSnapshot getInitialSnapshot(); 71 | 72 | default BoneSnapshot saveSnapshot() { 73 | return new BoneSnapshot(this); 74 | } 75 | 76 | String getName(); 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/builder/RawAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.builder; 7 | 8 | import java.util.Objects; 9 | 10 | import software.bernie.geckolib3.core.builder.ILoopType.EDefaultLoopTypes; 11 | 12 | public class RawAnimation { 13 | public String animationName; 14 | 15 | /** 16 | * If loop is null, the animation processor will use the loopByDefault boolean 17 | * to decide if the animation should loop. 18 | */ 19 | public ILoopType loopType; 20 | 21 | /** 22 | * A raw animation only stores the animation name and if it should loop, nothing 23 | * else 24 | * 25 | * @param animationName The name of the animation 26 | * @param loop Whether it should loop 27 | */ 28 | public RawAnimation(String animationName, ILoopType loop) { 29 | this.animationName = animationName; 30 | this.loopType = loop; 31 | } 32 | 33 | /** 34 | * Use {@link ILoopType} constructor 35 | */ 36 | @Deprecated(forRemoval = true) 37 | public RawAnimation(String animationName, boolean loop) { 38 | this(animationName, loop ? EDefaultLoopTypes.LOOP : EDefaultLoopTypes.PLAY_ONCE); 39 | } 40 | 41 | @Override 42 | public boolean equals(Object obj) { 43 | if (obj == this) 44 | return true; 45 | if (!(obj instanceof RawAnimation animation)) { 46 | return false; 47 | } 48 | return animation.loopType == this.loopType && animation.animationName.equals(this.animationName); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(this.animationName, this.loopType); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/AnimationPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.keyframe; 7 | 8 | public class AnimationPoint { 9 | /** 10 | * The current tick in the animation to lerp from 11 | */ 12 | public final double currentTick; 13 | /** 14 | * The tick that the current animation should end at 15 | */ 16 | public final double animationEndTick; 17 | /** 18 | * The Animation start value. 19 | */ 20 | public final double animationStartValue; 21 | /** 22 | * The Animation end value. 23 | */ 24 | public final double animationEndValue; 25 | 26 | /** 27 | * The current keyframe. 28 | */ 29 | public final KeyFrame keyframe; 30 | 31 | // Remove boxed arguments method, leaving this in place just incase an unforeseen issue arises 32 | /*public AnimationPoint(KeyFrame keyframe, double currentTick, Double animationEndTick, 33 | Double animationStartValue, double animationEndValue) { 34 | this.keyframe = keyframe; 35 | this.currentTick = currentTick; 36 | this.animationEndTick = animationEndTick; 37 | this.animationStartValue = animationStartValue; 38 | this.animationEndValue = animationEndValue; 39 | }*/ 40 | 41 | public AnimationPoint(KeyFrame keyframe, double tick, double animationEndTick, double animationStartValue, 42 | double animationEndValue) { 43 | this.keyframe = keyframe; 44 | this.currentTick = tick; 45 | this.animationEndTick = animationEndTick; 46 | this.animationStartValue = animationStartValue; 47 | this.animationEndValue = animationEndValue; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "Tick: " + currentTick + " | End Tick: " + animationEndTick + " | Start Value: " + animationStartValue 53 | + " | End Value: " + animationEndValue; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/VectorKeyFrameList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.keyframe; 7 | 8 | import java.util.List; 9 | 10 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 11 | 12 | /** 13 | * A vector key frame list is a handy class used to store 3 lists of keyframes: 14 | * the X, Y, and Z keyframes. The keyframes can be rotation, scale, or position. 15 | * 16 | * @param the type parameter 17 | */ 18 | public class VectorKeyFrameList { 19 | /** 20 | * The X key frames. 21 | */ 22 | public List xKeyFrames; 23 | /** 24 | * The Y key frames. 25 | */ 26 | public List yKeyFrames; 27 | /** 28 | * The Z key frames. 29 | */ 30 | public List zKeyFrames; 31 | 32 | /** 33 | * Instantiates a new vector key frame list from 3 lists of keyframes 34 | * 35 | * @param XKeyFrames the x key frames 36 | * @param YKeyFrames the y key frames 37 | * @param ZKeyFrames the z key frames 38 | */ 39 | public VectorKeyFrameList(List XKeyFrames, List YKeyFrames, List ZKeyFrames) { 40 | xKeyFrames = XKeyFrames; 41 | yKeyFrames = YKeyFrames; 42 | zKeyFrames = ZKeyFrames; 43 | } 44 | 45 | /** 46 | * Instantiates a new blank key frame list 47 | */ 48 | public VectorKeyFrameList() { 49 | xKeyFrames = new ObjectArrayList<>(); 50 | yKeyFrames = new ObjectArrayList<>(); 51 | zKeyFrames = new ObjectArrayList<>(); 52 | } 53 | 54 | public double getLastKeyframeTime() { 55 | double xTime = 0; 56 | for (T frame : xKeyFrames) { 57 | xTime += frame.getLength(); 58 | } 59 | 60 | double yTime = 0; 61 | for (T frame : yKeyFrames) { 62 | yTime += frame.getLength(); 63 | } 64 | 65 | double zTime = 0; 66 | for (T frame : zKeyFrames) { 67 | zTime += frame.getLength(); 68 | } 69 | 70 | return Math.max(xTime, Math.max(yTime, zTime)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/IAnimatableModel.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core; 2 | 3 | import software.bernie.geckolib3.core.builder.Animation; 4 | import software.bernie.geckolib3.core.event.predicate.AnimationEvent; 5 | import software.bernie.geckolib3.core.processor.AnimationProcessor; 6 | import software.bernie.geckolib3.core.processor.IBone; 7 | 8 | public interface IAnimatableModel { 9 | default double getCurrentTick() { 10 | return System.nanoTime() / 1000000L / 50d; 11 | } 12 | 13 | default void setCustomAnimations(E animatable, int instanceId) { 14 | setCustomAnimations(animatable, instanceId, null); 15 | } 16 | 17 | // TODO 1.20+ Remove default keyword 18 | default void setCustomAnimations(E animatable, int instanceId, AnimationEvent animationEvent) {} 19 | 20 | AnimationProcessor getAnimationProcessor(); 21 | 22 | Animation getAnimation(String name, IAnimatable animatable); 23 | 24 | /** 25 | * Gets a bone by name. 26 | * 27 | * @param boneName The bone name 28 | * @return the bone 29 | */ 30 | default IBone getBone(String boneName) { 31 | IBone bone = getAnimationProcessor().getBone(boneName); 32 | 33 | if (bone == null) 34 | throw new RuntimeException("Could not find bone: " + boneName); 35 | 36 | return bone; 37 | } 38 | 39 | void setMolangQueries(IAnimatable animatable, double seekTime); 40 | 41 | /** 42 | * Use {@link IAnimatableModel#setCustomAnimations(Object, int)}
43 | * Remove in 1.20+ 44 | */ 45 | @Deprecated(forRemoval = true) 46 | default void setLivingAnimations(E animatable, Integer instanceId) { 47 | setCustomAnimations(animatable, instanceId); 48 | } 49 | 50 | /** 51 | * Use {@link IAnimatableModel#setCustomAnimations(Object, int, AnimationEvent)}
52 | * Remove in 1.20+ 53 | */ 54 | @Deprecated(forRemoval = true) 55 | default void setLivingAnimations(E animatable, Integer instanceId, AnimationEvent animationEvent) { 56 | setCustomAnimations(animatable, instanceId, animationEvent); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/keyframe/KeyFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.keyframe; 7 | 8 | import java.util.List; 9 | import java.util.Objects; 10 | 11 | import it.unimi.dsi.fastutil.doubles.DoubleArrayList; 12 | import software.bernie.geckolib3.core.easing.EasingType; 13 | 14 | public class KeyFrame { 15 | private Double length; 16 | private T startValue; 17 | private T endValue; 18 | public EasingType easingType = EasingType.Linear; 19 | public List easingArgs = new DoubleArrayList(); 20 | 21 | public KeyFrame(Double length, T startValue, T endValue) { 22 | this.length = length; 23 | this.startValue = startValue; 24 | this.endValue = endValue; 25 | } 26 | 27 | public KeyFrame(Double length, T startValue, T endValue, EasingType easingType) { 28 | this.length = length; 29 | this.startValue = startValue; 30 | this.endValue = endValue; 31 | this.easingType = easingType; 32 | } 33 | 34 | public KeyFrame(Double length, T startValue, T endValue, EasingType easingType, List easingArgs) { 35 | this.length = length; 36 | this.startValue = startValue; 37 | this.endValue = endValue; 38 | this.easingType = easingType; 39 | this.easingArgs = easingArgs; 40 | } 41 | 42 | public Double getLength() { 43 | return length; 44 | } 45 | 46 | public void setLength(Double length) { 47 | this.length = length; 48 | } 49 | 50 | public T getStartValue() { 51 | return startValue; 52 | } 53 | 54 | public void setStartValue(T startValue) { 55 | this.startValue = startValue; 56 | } 57 | 58 | public T getEndValue() { 59 | return endValue; 60 | } 61 | 62 | public void setEndValue(T endValue) { 63 | this.endValue = endValue; 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return Objects.hash(length, startValue, endValue); 69 | } 70 | 71 | @Override 72 | public boolean equals(Object obj) { 73 | return obj instanceof KeyFrame && hashCode() == obj.hashCode(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/manager/AnimationFactory.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.manager; 2 | 3 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 4 | import software.bernie.geckolib3.core.IAnimatable; 5 | 6 | /** 7 | * TODO 1.20+: 8 | *
    9 | *
  • Remove {@code animationDataMap}
  • 10 | *
  • Make {@code AnimationFactory} abstract
  • 11 | *
  • Make {@code getOrCreateAnimationData} abstract
  • 12 | *
13 | */ 14 | public class AnimationFactory { 15 | protected final IAnimatable animatable; 16 | private final Int2ObjectOpenHashMap animationDataMap = new Int2ObjectOpenHashMap<>(); 17 | 18 | /** 19 | * Deprecated, use {@code GeckolibUtil#createFactory(IAnimatable)} 20 | * 21 | * @param animatable The animatable object the factory is for 22 | */ 23 | @Deprecated(forRemoval = true) 24 | public AnimationFactory(IAnimatable animatable) { 25 | this.animatable = animatable; 26 | } 27 | 28 | /** 29 | * This creates or gets the cached animation manager for any unique ID. For 30 | * itemstacks, this is typically a hashcode of their nbt. For entities it should 31 | * be their unique uuid. For tile entities you can use nbt or just one constant 32 | * value since they are not singletons. 33 | * 34 | * @param uniqueID A unique integer ID. For every ID the same animation manager 35 | * will be returned. 36 | * @return the animatable manager 37 | */ 38 | public AnimationData getOrCreateAnimationData(int uniqueID) { 39 | if (!this.animationDataMap.containsKey(uniqueID)) { 40 | AnimationData data = new AnimationData(); 41 | 42 | this.animatable.registerControllers(data); 43 | this.animationDataMap.put(uniqueID, data); 44 | } 45 | 46 | return animationDataMap.get(uniqueID); 47 | } 48 | 49 | /** 50 | * Use {@link AnimationFactory#getOrCreateAnimationData(int)} 51 | */ 52 | @Deprecated(forRemoval = true) 53 | public AnimationData getOrCreateAnimationData(Integer uniqueID) { 54 | return getOrCreateAnimationData((int)uniqueID); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/easing/EasingType.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.easing; 2 | 3 | import java.util.Locale; 4 | 5 | public enum EasingType { 6 | NONE, CUSTOM, Linear, Step, EaseInSine, EaseOutSine, EaseInOutSine, EaseInQuad, EaseOutQuad, EaseInOutQuad, 7 | EaseInCubic, EaseOutCubic, EaseInOutCubic, EaseInQuart, EaseOutQuart, EaseInOutQuart, EaseInQuint, EaseOutQuint, 8 | EaseInOutQuint, EaseInExpo, EaseOutExpo, EaseInOutExpo, EaseInCirc, EaseOutCirc, EaseInOutCirc, EaseInBack, 9 | EaseOutBack, EaseInOutBack, EaseInElastic, EaseOutElastic, EaseInOutElastic, EaseInBounce, EaseOutBounce, 10 | EaseInOutBounce; 11 | 12 | public static EasingType getEasingTypeFromString(String search) { 13 | return switch (search.toLowerCase(Locale.ROOT)) { 14 | default -> NONE; 15 | case "custom" -> CUSTOM; 16 | case "linear" -> Linear; 17 | case "step" -> Step; 18 | case "easeinsine" -> EaseInSine; 19 | case "easeoutsine" -> EaseOutSine; 20 | case "easeinoutsine" -> EaseInOutSine; 21 | case "easeinquad" -> EaseInQuad; 22 | case "easeoutquad" -> EaseOutQuad; 23 | case "easeinoutquad" -> EaseInOutQuad; 24 | case "easeincubic" -> EaseInCubic; 25 | case "easeoutcubic" -> EaseOutCubic; 26 | case "easeinoutcubic" -> EaseInOutCubic; 27 | case "easeinquart" -> EaseInQuart; 28 | case "easeoutquart" -> EaseOutQuart; 29 | case "easeinoutquart" -> EaseInOutQuart; 30 | case "easeinquint" -> EaseInQuint; 31 | case "easeoutquint" -> EaseOutQuint; 32 | case "easeinoutquint" -> EaseInOutQuint; 33 | case "easeinexpo" -> EaseInExpo; 34 | case "easeoutexpo" -> EaseOutExpo; 35 | case "easeinoutexpo" -> EaseInOutExpo; 36 | case "easeincirc" -> EaseInCirc; 37 | case "easeoutcirc" -> EaseOutCirc; 38 | case "easeinoutcirc" -> EaseInOutCirc; 39 | case "easeinback" -> EaseInBack; 40 | case "easeoutback" -> EaseOutBack; 41 | case "easeinoutback" -> EaseInOutBack; 42 | case "easeinelastic" -> EaseInElastic; 43 | case "easeoutelastic" -> EaseOutElastic; 44 | case "easeinoutelastic" -> EaseInOutElastic; 45 | case "easeinbounce" -> EaseInBounce; 46 | case "easeoutbounce" -> EaseOutBounce; 47 | case "easeinoutbounce" -> EaseInOutBounce; 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/event/predicate/AnimationEvent.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.event.predicate; 2 | 3 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 4 | import software.bernie.geckolib3.core.IAnimatable; 5 | import software.bernie.geckolib3.core.controller.AnimationController; 6 | 7 | import java.util.List; 8 | 9 | public class AnimationEvent { 10 | private final T animatable; 11 | public double animationTick; 12 | private final float limbSwing; 13 | private final float limbSwingAmount; 14 | private final float partialTick; 15 | private final boolean isMoving; 16 | private final List extraData; 17 | protected AnimationController controller; 18 | 19 | public AnimationEvent(T animatable, float limbSwing, float limbSwingAmount, float partialTick, boolean isMoving, 20 | List extraData) { 21 | this.animatable = animatable; 22 | this.limbSwing = limbSwing; 23 | this.limbSwingAmount = limbSwingAmount; 24 | this.partialTick = partialTick; 25 | this.isMoving = isMoving; 26 | this.extraData = extraData; 27 | } 28 | 29 | /** 30 | * Gets the amount of ticks that have passed in either the current transition or 31 | * animation, depending on the controller's AnimationState. 32 | * 33 | * @return the animation tick 34 | */ 35 | public double getAnimationTick() { 36 | return animationTick; 37 | } 38 | 39 | public T getAnimatable() { 40 | return animatable; 41 | } 42 | 43 | public float getLimbSwing() { 44 | return limbSwing; 45 | } 46 | 47 | public float getLimbSwingAmount() { 48 | return limbSwingAmount; 49 | } 50 | 51 | public float getPartialTick() { 52 | return partialTick; 53 | } 54 | 55 | public boolean isMoving() { 56 | return isMoving; 57 | } 58 | 59 | public AnimationController getController() { 60 | return controller; 61 | } 62 | 63 | public void setController(AnimationController controller) { 64 | this.controller = controller; 65 | } 66 | 67 | public List getExtraData() { 68 | return extraData; 69 | } 70 | 71 | public List getExtraDataOfType(Class type) { 72 | ObjectArrayList matches = new ObjectArrayList<>(); 73 | 74 | for (Object obj : this.extraData) { 75 | if (type.isAssignableFrom(obj.getClass())) 76 | matches.add((D)obj); 77 | } 78 | 79 | return matches; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/util/MathUtil.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.util; 2 | 3 | import software.bernie.geckolib3.core.easing.EasingManager; 4 | import software.bernie.geckolib3.core.easing.EasingType; 5 | import software.bernie.geckolib3.core.keyframe.AnimationPoint; 6 | 7 | import java.util.function.Function; 8 | 9 | public class MathUtil { 10 | /** 11 | * Lerps an AnimationPoint 12 | * 13 | * @param animationPoint The animation point 14 | * @return the resulting lerped value 15 | */ 16 | public static float lerpValues(AnimationPoint animationPoint, EasingType easingType, 17 | Function customEasingMethod) { 18 | if (animationPoint.currentTick >= animationPoint.animationEndTick) { 19 | return (float)animationPoint.animationEndValue; 20 | } 21 | if (animationPoint.currentTick == 0 && animationPoint.animationEndTick == 0) { 22 | return (float)animationPoint.animationEndValue; 23 | } 24 | 25 | if (easingType == EasingType.CUSTOM && customEasingMethod != null) { 26 | return lerpValues(customEasingMethod.apply(animationPoint.currentTick / animationPoint.animationEndTick), 27 | animationPoint.animationStartValue, animationPoint.animationEndValue); 28 | } else if (easingType == EasingType.NONE && animationPoint.keyframe != null) { 29 | easingType = animationPoint.keyframe.easingType; 30 | } 31 | double ease = EasingManager.ease(animationPoint.currentTick / animationPoint.animationEndTick, easingType, 32 | animationPoint.keyframe == null ? null : animationPoint.keyframe.easingArgs); 33 | return lerpValues(ease, animationPoint.animationStartValue, animationPoint.animationEndValue); 34 | } 35 | 36 | /** 37 | * This is the actual function that smoothly interpolates (lerp) between 38 | * keyframes 39 | * 40 | * @param startValue The animation's start value 41 | * @param endValue The animation's end value 42 | * @return The interpolated value 43 | */ 44 | public static float lerpValues(double percentCompleted, double startValue, double endValue) { 45 | // current tick / position should be between 0 and 1 and represent the 46 | // percentage of the lerping that has completed 47 | return (float) lerp(percentCompleted, startValue, endValue); 48 | } 49 | 50 | public static double lerp(double pct, double start, double end) { 51 | return start + pct * (end - start); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/manager/AnimationData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.manager; 7 | 8 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 9 | import org.apache.commons.lang3.tuple.Pair; 10 | import software.bernie.geckolib3.core.controller.AnimationController; 11 | import software.bernie.geckolib3.core.processor.IBone; 12 | import software.bernie.geckolib3.core.snapshot.BoneSnapshot; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | public class AnimationData { 18 | private Map> boneSnapshotCollection; 19 | private Map animationControllers = new Object2ObjectOpenHashMap<>(); 20 | public double tick; 21 | public boolean isFirstTick = true; 22 | private double resetTickLength = 1; 23 | public double startTick = -1; 24 | public Object ticker; 25 | public boolean shouldPlayWhilePaused = false; 26 | 27 | /** 28 | * Instantiates a new Animation controller collection. 29 | */ 30 | public AnimationData() { 31 | super(); 32 | boneSnapshotCollection = new Object2ObjectOpenHashMap<>(); 33 | } 34 | 35 | /** 36 | * This method is how you register animation controllers, without this, your 37 | * AnimationPredicate method will never be called 38 | * 39 | * @param value The value 40 | * @return the animation controller 41 | */ 42 | public AnimationController addAnimationController(AnimationController value) { 43 | return this.animationControllers.put(value.getName(), value); 44 | } 45 | 46 | public Map> getBoneSnapshotCollection() { 47 | return boneSnapshotCollection; 48 | } 49 | 50 | public void setBoneSnapshotCollection(HashMap> boneSnapshotCollection) { 51 | this.boneSnapshotCollection = boneSnapshotCollection; 52 | } 53 | 54 | public void clearSnapshotCache() { 55 | this.boneSnapshotCollection = new HashMap<>(); 56 | } 57 | 58 | public double getResetSpeed() { 59 | return resetTickLength; 60 | } 61 | 62 | /** 63 | * This is how long it takes for any bones that don't have an animation to 64 | * revert back to their original position 65 | * 66 | * @param resetTickLength The amount of ticks it takes to reset. Cannot be 67 | * negative. 68 | */ 69 | public void setResetSpeedInTicks(double resetTickLength) { 70 | this.resetTickLength = resetTickLength < 0 ? 0 : resetTickLength; 71 | } 72 | 73 | public Map getAnimationControllers() { 74 | return animationControllers; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/snapshot/BoneSnapshot.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.snapshot; 7 | 8 | import software.bernie.geckolib3.core.processor.IBone; 9 | 10 | public class BoneSnapshot { 11 | public BoneSnapshot(IBone modelRenderer) { 12 | rotationValueX = modelRenderer.getRotationX(); 13 | rotationValueY = modelRenderer.getRotationY(); 14 | rotationValueZ = modelRenderer.getRotationZ(); 15 | 16 | positionOffsetX = modelRenderer.getPositionX(); 17 | positionOffsetY = modelRenderer.getPositionY(); 18 | positionOffsetZ = modelRenderer.getPositionZ(); 19 | 20 | scaleValueX = modelRenderer.getScaleX(); 21 | scaleValueY = modelRenderer.getScaleY(); 22 | scaleValueZ = modelRenderer.getScaleZ(); 23 | 24 | this.modelRenderer = modelRenderer; 25 | this.name = modelRenderer.getName(); 26 | } 27 | 28 | public BoneSnapshot(IBone modelRenderer, boolean dontSaveRotations) { 29 | if (dontSaveRotations) { 30 | rotationValueX = 0; 31 | rotationValueY = 0; 32 | rotationValueZ = 0; 33 | } 34 | 35 | rotationValueX = modelRenderer.getRotationX(); 36 | rotationValueY = modelRenderer.getRotationY(); 37 | rotationValueZ = modelRenderer.getRotationZ(); 38 | 39 | positionOffsetX = modelRenderer.getPositionX(); 40 | positionOffsetY = modelRenderer.getPositionY(); 41 | positionOffsetZ = modelRenderer.getPositionZ(); 42 | 43 | scaleValueX = modelRenderer.getScaleX(); 44 | scaleValueY = modelRenderer.getScaleY(); 45 | scaleValueZ = modelRenderer.getScaleZ(); 46 | 47 | this.modelRenderer = modelRenderer; 48 | this.name = modelRenderer.getName(); 49 | } 50 | 51 | public BoneSnapshot(BoneSnapshot snapshot) { 52 | scaleValueX = snapshot.scaleValueX; 53 | scaleValueY = snapshot.scaleValueY; 54 | scaleValueZ = snapshot.scaleValueZ; 55 | 56 | positionOffsetX = snapshot.positionOffsetX; 57 | positionOffsetY = snapshot.positionOffsetY; 58 | positionOffsetZ = snapshot.positionOffsetZ; 59 | 60 | rotationValueX = snapshot.rotationValueX; 61 | rotationValueY = snapshot.rotationValueY; 62 | rotationValueZ = snapshot.rotationValueZ; 63 | this.modelRenderer = snapshot.modelRenderer; 64 | this.name = snapshot.name; 65 | } 66 | 67 | public String name; 68 | private IBone modelRenderer; 69 | 70 | public float scaleValueX; 71 | public float scaleValueY; 72 | public float scaleValueZ; 73 | 74 | public float positionOffsetX; 75 | public float positionOffsetY; 76 | public float positionOffsetZ; 77 | 78 | public float rotationValueX; 79 | public float rotationValueY; 80 | public float rotationValueZ; 81 | 82 | public float mostRecentResetRotationTick = 0; 83 | public float mostRecentResetPositionTick = 0; 84 | public float mostRecentResetScaleTick = 0; 85 | 86 | public boolean isCurrentlyRunningRotationAnimation = true; 87 | public boolean isCurrentlyRunningPositionAnimation = true; 88 | public boolean isCurrentlyRunningScaleAnimation = true; 89 | 90 | @Override 91 | public boolean equals(Object o) { 92 | if (this == o) { 93 | return true; 94 | } 95 | if (o == null || getClass() != o.getClass()) { 96 | return false; 97 | } 98 | BoneSnapshot that = (BoneSnapshot) o; 99 | return name.equals(that.name); 100 | } 101 | 102 | @Override 103 | public int hashCode() { 104 | return name.hashCode(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/builder/AnimationBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.builder; 7 | 8 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 9 | import software.bernie.geckolib3.core.builder.ILoopType.EDefaultLoopTypes; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * This class follows the builder pattern, which means that every method returns 15 | * an instance of this class. You can stack method calls, like this: 16 | * new AnimationBuilder().addAnimation("jump").addRepeatingAnimation("run", 5"); 17 | */ 18 | public class AnimationBuilder { 19 | private final List animationList = new ObjectArrayList<>(); 20 | 21 | /** 22 | * Add a single animation to the queue and overrides the loop setting 23 | * 24 | * @param animationName The name of the animation. MUST MATCH THE NAME OF THE 25 | * ANIMATION IN THE BLOCKBENCH FILE 26 | * @param loopType loop 27 | * @return An instance of the current animation builder 28 | */ 29 | public AnimationBuilder addAnimation(String animationName, ILoopType loopType) { 30 | animationList.add(new RawAnimation(animationName, loopType)); 31 | return this; 32 | } 33 | 34 | /** 35 | * Use {@link AnimationBuilder#addAnimation(String, ILoopType)} 36 | */ 37 | @Deprecated(forRemoval = true) 38 | public AnimationBuilder addAnimation(String animationName, Boolean shouldLoop) { 39 | animationList.add(new RawAnimation(animationName, shouldLoop)); 40 | return this; 41 | } 42 | 43 | /** 44 | * Add a single animation to the queue 45 | * 46 | * @param animationName The name of the animation. MUST MATCH THE NAME OF THE 47 | * ANIMATION IN THE BLOCKBENCH FILE 48 | * @return An instance of the current animation builder 49 | */ 50 | public AnimationBuilder addAnimation(String animationName) { 51 | animationList.add(new RawAnimation(animationName, null)); 52 | return this; 53 | } 54 | 55 | /** 56 | * Add multiple animations to the queue and overrides the loop setting to false 57 | * 58 | * @param animationName The name of the animation. MUST MATCH THE NAME OF THE 59 | * ANIMATION IN THE BLOCKBENCH FILE 60 | * @param timesToRepeat How many times to add the animation to the queue 61 | * @return An instance of the current animation builder 62 | */ 63 | public AnimationBuilder addRepeatingAnimation(String animationName, int timesToRepeat) { 64 | assert timesToRepeat > 0; 65 | for (int i = 0; i < timesToRepeat; i++) { 66 | addAnimation(animationName, EDefaultLoopTypes.PLAY_ONCE); 67 | } 68 | return this; 69 | } 70 | 71 | public AnimationBuilder playOnce(String animationName) { 72 | return this.addAnimation(animationName, EDefaultLoopTypes.PLAY_ONCE); 73 | } 74 | 75 | public AnimationBuilder loop(String animationName) { 76 | return this.addAnimation(animationName, EDefaultLoopTypes.LOOP); 77 | } 78 | 79 | /* 80 | * Not implemented yet! 81 | */ 82 | public AnimationBuilder playAndHold(String animationName) { 83 | return this.addAnimation(animationName, EDefaultLoopTypes.HOLD_ON_LAST_FRAME); 84 | } 85 | 86 | //Below will use "Wait instructions", basically empty animations that do nothing, not sure if we really need those honestly 87 | public AnimationBuilder delayNext(int waitTimeTicks) { 88 | throw new UnsupportedOperationException("This isn't implemented yet, sorry!"); 89 | } 90 | 91 | public AnimationBuilder playAndHoldFor(String animationName, int waitTimeTicks) { 92 | this.playAndHold(animationName); 93 | return this.delayNext(waitTimeTicks); 94 | } 95 | 96 | /** 97 | * Clear all the animations in the animation builder. 98 | * 99 | * @return An instance of the current animation builder 100 | */ 101 | public AnimationBuilder clearAnimations() { 102 | animationList.clear(); 103 | return this; 104 | } 105 | 106 | /** 107 | * Gets the animations currently in this builder. 108 | */ 109 | public List getRawAnimationList() { 110 | return animationList; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/util/Color.java: -------------------------------------------------------------------------------- 1 | /* 2 | Direct copy of https://github.com/shedaniel/cloth-basic-math/blob/master/src/main/java/me/shedaniel/math/Color.java under the unlicense. 3 | */ 4 | package software.bernie.geckolib3.core.util; 5 | 6 | public final class Color { 7 | private final int color; 8 | 9 | public static final Color WHITE = new Color(0xFFFFFFFF); 10 | 11 | public static final Color LIGHT_GRAY = new Color(0xFFC0C0C0); 12 | 13 | public static final Color GRAY = new Color(0xFF808080); 14 | 15 | public static final Color DARK_GRAY = new Color(0xFF404040); 16 | 17 | public static final Color BLACK = new Color(0xFF000000); 18 | 19 | public static final Color RED = new Color(0xFFFF0000); 20 | 21 | public static final Color PINK = new Color(0xFFFFAFAF); 22 | 23 | public static final Color ORANGE = new Color(0xFFFFC800); 24 | 25 | public static final Color YELLOW = new Color(0xFFFFFF00); 26 | 27 | public static final Color GREEN = new Color(0xFF00FF00); 28 | 29 | public static final Color MAGENTA = new Color(0xFFFF00FF); 30 | 31 | public static final Color CYAN = new Color(0xFF00FFFF); 32 | 33 | public static final Color BLUE = new Color(0xFF0000FF); 34 | 35 | private Color(int color) { 36 | this.color = color; 37 | } 38 | 39 | public static Color ofTransparent(int color) { 40 | return new Color(color); 41 | } 42 | 43 | public static Color ofOpaque(int color) { 44 | return new Color(0xFF000000 | color); 45 | } 46 | 47 | public static Color ofRGB(float r, float g, float b) { 48 | return ofRGBA(r, g, b, 1f); 49 | } 50 | 51 | public static Color ofRGB(int r, int g, int b) { 52 | return ofRGBA(r, g, b, 255); 53 | } 54 | 55 | public static Color ofRGBA(float r, float g, float b, float a) { 56 | return ofRGBA((int) (r * 255 + 0.5), (int) (g * 255 + 0.5), (int) (b * 255 + 0.5), (int) (a * 255 + 0.5)); 57 | } 58 | 59 | public static Color ofRGBA(int r, int g, int b, int a) { 60 | return new Color(((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF)); 61 | } 62 | 63 | public static Color ofHSB(float hue, float saturation, float brightness) { 64 | return ofOpaque(HSBtoRGB(hue, saturation, brightness)); 65 | } 66 | 67 | public static int HSBtoRGB(float hue, float saturation, float brightness) { 68 | int r = 0, g = 0, b = 0; 69 | if (saturation == 0) { 70 | r = g = b = (int) (brightness * 255.0f + 0.5f); 71 | } else { 72 | float h = (hue - (float) Math.floor(hue)) * 6.0f; 73 | float f = h - (float) Math.floor(h); 74 | float p = brightness * (1.0f - saturation); 75 | float q = brightness * (1.0f - saturation * f); 76 | float t = brightness * (1.0f - (saturation * (1.0f - f))); 77 | switch ((int) h) { 78 | case 0 -> { 79 | r = (int) (brightness * 255.0f + 0.5f); 80 | g = (int) (t * 255.0f + 0.5f); 81 | b = (int) (p * 255.0f + 0.5f); 82 | } 83 | case 1 -> { 84 | r = (int) (q * 255.0f + 0.5f); 85 | g = (int) (brightness * 255.0f + 0.5f); 86 | b = (int) (p * 255.0f + 0.5f); 87 | } 88 | case 2 -> { 89 | r = (int) (p * 255.0f + 0.5f); 90 | g = (int) (brightness * 255.0f + 0.5f); 91 | b = (int) (t * 255.0f + 0.5f); 92 | } 93 | case 3 -> { 94 | r = (int) (p * 255.0f + 0.5f); 95 | g = (int) (q * 255.0f + 0.5f); 96 | b = (int) (brightness * 255.0f + 0.5f); 97 | } 98 | case 4 -> { 99 | r = (int) (t * 255.0f + 0.5f); 100 | g = (int) (p * 255.0f + 0.5f); 101 | b = (int) (brightness * 255.0f + 0.5f); 102 | } 103 | case 5 -> { 104 | r = (int) (brightness * 255.0f + 0.5f); 105 | g = (int) (p * 255.0f + 0.5f); 106 | b = (int) (q * 255.0f + 0.5f); 107 | } 108 | } 109 | } 110 | return 0xff000000 | (r << 16) | (g << 8) | b; 111 | } 112 | 113 | public int getColor() { 114 | return color; 115 | } 116 | 117 | public int getAlpha() { 118 | return color >> 24 & 0xFF; 119 | } 120 | 121 | public int getRed() { 122 | return color >> 16 & 0xFF; 123 | } 124 | 125 | public int getGreen() { 126 | return color >> 8 & 0xFF; 127 | } 128 | 129 | public int getBlue() { 130 | return color & 0xFF; 131 | } 132 | 133 | /** 134 | * Returns a brighter color 135 | * 136 | * @param factor the higher the value, the brighter the color 137 | * @return the brighter color 138 | */ 139 | public Color brighter(double factor) { 140 | int r = getRed(), g = getGreen(), b = getBlue(); 141 | int i = (int) (1.0 / (1.0 - (1 / factor))); 142 | if (r == 0 && g == 0 && b == 0) { 143 | return ofRGBA(i, i, i, getAlpha()); 144 | } 145 | if (r > 0 && r < i) 146 | r = i; 147 | if (g > 0 && g < i) 148 | g = i; 149 | if (b > 0 && b < i) 150 | b = i; 151 | return ofRGBA(Math.min((int) (r / (1 / factor)), 255), Math.min((int) (g / (1 / factor)), 255), 152 | Math.min((int) (b / (1 / factor)), 255), getAlpha()); 153 | } 154 | 155 | /** 156 | * Returns a darker color 157 | * 158 | * @param factor the higher the value, the darker the color 159 | * @return the darker color 160 | */ 161 | public Color darker(double factor) { 162 | return ofRGBA(Math.max((int) (getRed() * (1 / factor)), 0), Math.max((int) (getGreen() * (1 / factor)), 0), 163 | Math.max((int) (getBlue() * (1 / factor)), 0), getAlpha()); 164 | } 165 | 166 | @Override 167 | public boolean equals(Object other) { 168 | if (this == other) 169 | return true; 170 | if (other == null || getClass() != other.getClass()) 171 | return false; 172 | return color == ((Color) other).color; 173 | } 174 | 175 | @Override 176 | public int hashCode() { 177 | return color; 178 | } 179 | 180 | @Override 181 | public String toString() { 182 | return String.valueOf(color); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/molang/MolangParser.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.molang; 2 | 3 | import com.eliotlash.mclib.math.Constant; 4 | import com.eliotlash.mclib.math.IValue; 5 | import com.eliotlash.mclib.math.MathBuilder; 6 | import com.eliotlash.mclib.math.Variable; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonPrimitive; 9 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 10 | import software.bernie.geckolib3.core.molang.expressions.MolangAssignment; 11 | import software.bernie.geckolib3.core.molang.expressions.MolangExpression; 12 | import software.bernie.geckolib3.core.molang.expressions.MolangMultiStatement; 13 | import software.bernie.geckolib3.core.molang.expressions.MolangValue; 14 | import software.bernie.geckolib3.core.molang.functions.CosDegrees; 15 | import software.bernie.geckolib3.core.molang.functions.SinDegrees; 16 | 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.function.DoubleSupplier; 20 | 21 | /** 22 | * MoLang parser 23 | * This bad boy parses Molang expressions 24 | * https://bedrock.dev/docs/1.19.0.0/1.19.30.23/Molang#Math%20Functions 25 | */ 26 | public class MolangParser extends MathBuilder { 27 | // Replace base variables map 28 | public static final Map VARIABLES = new Object2ObjectOpenHashMap<>(); 29 | public static final MolangExpression ZERO = new MolangValue(null, new Constant(0)); 30 | public static final MolangExpression ONE = new MolangValue(null, new Constant(1)); 31 | public static final String RETURN = "return "; 32 | 33 | public MolangParser() { 34 | super(); 35 | 36 | // Remap functions to be intact with Molang specification 37 | doCoreRemaps(); 38 | registerAdditionalVariables(); 39 | } 40 | 41 | private void doCoreRemaps() { 42 | // Replace radian based sin and cos with degree-based functions 43 | this.functions.put("cos", CosDegrees.class); 44 | this.functions.put("sin", SinDegrees.class); 45 | 46 | remap("abs", "math.abs"); 47 | remap("acos", "math.acos"); 48 | remap("asin", "math.asin"); 49 | remap("atan", "math.atan"); 50 | remap("atan2", "math.atan2"); 51 | remap("ceil", "math.ceil"); 52 | remap("clamp", "math.clamp"); 53 | remap("cos", "math.cos"); 54 | remap("die_roll", "math.die_roll"); 55 | remap("die_roll_integer", "math.die_roll_integer"); 56 | remap("exp", "math.exp"); 57 | remap("floor", "math.floor"); 58 | remap("hermite_blend", "math.hermite_blend"); 59 | remap("lerp", "math.lerp"); 60 | remap("lerprotate", "math.lerprotate"); 61 | remap("ln", "math.ln"); 62 | remap("max", "math.max"); 63 | remap("min", "math.min"); 64 | remap("mod", "math.mod"); 65 | remap("pi", "math.pi"); 66 | remap("pow", "math.pow"); 67 | remap("random", "math.random"); 68 | remap("random_integer", "math.random_integer"); 69 | remap("round", "math.round"); 70 | remap("sin", "math.sin"); 71 | remap("sqrt", "math.sqrt"); 72 | remap("trunc", "math.trunc"); 73 | } 74 | 75 | private void registerAdditionalVariables() { 76 | register(new LazyVariable("query.anim_time", 0)); 77 | register(new LazyVariable("query.actor_count", 0)); 78 | register(new LazyVariable("query.health", 0)); 79 | register(new LazyVariable("query.max_health", 0)); 80 | register(new LazyVariable("query.distance_from_camera", 0)); 81 | register(new LazyVariable("query.yaw_speed", 0)); 82 | register(new LazyVariable("query.is_in_water_or_rain", 0)); 83 | register(new LazyVariable("query.is_in_water", 0)); 84 | register(new LazyVariable("query.is_on_ground", 0)); 85 | register(new LazyVariable("query.time_of_day", 0)); 86 | register(new LazyVariable("query.is_on_fire", 0)); 87 | register(new LazyVariable("query.ground_speed", 0)); 88 | } 89 | 90 | @Override 91 | public void register(Variable variable) { 92 | if (!(variable instanceof LazyVariable)) 93 | variable = LazyVariable.from(variable); 94 | 95 | VARIABLES.put(variable.getName(), (LazyVariable)variable); 96 | } 97 | 98 | /** 99 | * Remap function names 100 | */ 101 | public void remap(String old, String newName) { 102 | this.functions.put(newName, this.functions.remove(old)); 103 | } 104 | 105 | /** 106 | * Deprecated, use {@link MolangParser#setValue(String, DoubleSupplier)} 107 | */ 108 | @Deprecated(forRemoval = true) 109 | public void setValue(String name, double value) { 110 | setValue(name, () -> value); 111 | } 112 | 113 | public void setValue(String name, DoubleSupplier value) { 114 | LazyVariable variable = getVariable(name); 115 | 116 | if (variable != null) 117 | variable.set(value); 118 | } 119 | 120 | @Override 121 | protected LazyVariable getVariable(String name) { 122 | return VARIABLES.computeIfAbsent(name, key -> new LazyVariable(key, 0)); 123 | } 124 | 125 | public LazyVariable getVariable(String name, MolangMultiStatement currentStatement) { 126 | LazyVariable variable; 127 | 128 | if (currentStatement != null) { 129 | variable = currentStatement.locals.get(name); 130 | 131 | if (variable != null) 132 | return variable; 133 | } 134 | 135 | return getVariable(name); 136 | } 137 | 138 | public MolangExpression parseJson(JsonElement element) throws MolangException { 139 | if (!element.isJsonPrimitive()) 140 | return ZERO; 141 | 142 | JsonPrimitive primitive = element.getAsJsonPrimitive(); 143 | 144 | if (primitive.isNumber()) 145 | return new MolangValue(this, new Constant(primitive.getAsDouble())); 146 | 147 | if (primitive.isString()) { 148 | String string = primitive.getAsString(); 149 | 150 | try { 151 | return new MolangValue(this, new Constant(Double.parseDouble(string))); 152 | } 153 | catch (NumberFormatException ex) { 154 | return parseExpression(string); 155 | } 156 | } 157 | 158 | return ZERO; 159 | } 160 | 161 | /** 162 | * Parse a molang expression 163 | */ 164 | public MolangExpression parseExpression(String expression) throws MolangException { 165 | MolangMultiStatement result = null; 166 | 167 | for (String split : expression.toLowerCase().trim().split(";")) { 168 | String trimmed = split.trim(); 169 | 170 | if (!trimmed.isEmpty()) { 171 | if (result == null) 172 | result = new MolangMultiStatement(this); 173 | 174 | result.expressions.add(parseOneLine(trimmed, result)); 175 | } 176 | } 177 | 178 | if (result == null) 179 | throw new MolangException("Molang expression cannot be blank!"); 180 | 181 | return result; 182 | } 183 | 184 | /** 185 | * Parse a single Molang statement 186 | */ 187 | protected MolangExpression parseOneLine(String expression, MolangMultiStatement currentStatement) throws MolangException { 188 | if (expression.startsWith(RETURN)) { 189 | try { 190 | return new MolangValue(this, parse(expression.substring(RETURN.length()))).addReturn(); 191 | } 192 | catch (Exception e) { 193 | throw new MolangException("Couldn't parse return '" + expression + "' expression!"); 194 | } 195 | } 196 | 197 | try { 198 | List symbols = breakdownChars(this.breakdown(expression)); 199 | 200 | /* Assignment it is */ 201 | if (symbols.size() >= 3 && symbols.get(0) instanceof String && isVariable(symbols.get(0)) && symbols.get(1).equals("=")) { 202 | String name = (String)symbols.get(0); 203 | symbols = symbols.subList(2, symbols.size()); 204 | LazyVariable variable; 205 | 206 | if (!VARIABLES.containsKey(name) && !currentStatement.locals.containsKey(name)) { 207 | currentStatement.locals.put(name, (variable = new LazyVariable(name, 0))); 208 | } 209 | else { 210 | variable = getVariable(name, currentStatement); 211 | } 212 | 213 | return new MolangAssignment(this, variable, parseSymbolsMolang(symbols)); 214 | } 215 | 216 | return new MolangValue(this, parseSymbolsMolang(symbols)); 217 | } 218 | catch (Exception e) { 219 | throw new MolangException("Couldn't parse '" + expression + "' expression!"); 220 | } 221 | } 222 | 223 | /** 224 | * Wrapper around {@link #parseSymbols(List)} to throw {@link MolangException} 225 | */ 226 | private IValue parseSymbolsMolang(List symbols) throws MolangException { 227 | try { 228 | return this.parseSymbols(symbols); 229 | } 230 | catch (Exception e) { 231 | e.printStackTrace(); 232 | 233 | throw new MolangException("Couldn't parse an expression!"); 234 | } 235 | } 236 | 237 | /** 238 | * Extend this method to allow {@link #breakdownChars(String[])} to capture "=" 239 | * as an operator, so it was easier to parse assignment statements 240 | */ 241 | @Override 242 | protected boolean isOperator(String s) { 243 | return super.isOperator(s) || s.equals("="); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/easing/EasingManager.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.easing; 2 | 3 | import java.util.List; 4 | import java.util.function.Function; 5 | 6 | import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; 7 | import software.bernie.geckolib3.core.util.Memoizer; 8 | 9 | public class EasingManager { 10 | 11 | public static double ease(double number, EasingType easingType, List easingArgs) { 12 | Double firstArg = easingArgs == null || easingArgs.size() < 1 ? null : easingArgs.get(0); 13 | return getEasingFunction.apply(new EasingFunctionArgs(easingType, firstArg)).apply(number); 14 | } 15 | 16 | // Memoize easing functions so that we don't need to create new ones from HOFs every 17 | // frame 18 | static Double2DoubleFunction quart = poly(4); 19 | static Double2DoubleFunction quint = poly(5); 20 | static Function getEasingFunction = Memoizer 21 | .memoize(EasingManager::getEasingFuncImpl); 22 | 23 | // Don't call this, use getEasingFunction instead as that function is the 24 | // memoized version 25 | static Double2DoubleFunction getEasingFuncImpl(EasingFunctionArgs args) { 26 | return switch (args.easingType()) { 27 | default -> in(EasingManager::linear); 28 | case Step -> in(step(args.arg0())); 29 | case EaseInSine -> in(EasingManager::sin); 30 | case EaseOutSine -> out(EasingManager::sin); 31 | case EaseInOutSine -> inOut(EasingManager::sin); 32 | case EaseInQuad -> in(EasingManager::quad); 33 | case EaseOutQuad -> out(EasingManager::quad); 34 | case EaseInOutQuad -> inOut(EasingManager::quad); 35 | case EaseInCubic -> in(EasingManager::cubic); 36 | case EaseOutCubic -> out(EasingManager::cubic); 37 | case EaseInOutCubic -> inOut(EasingManager::cubic); 38 | case EaseInExpo -> in(EasingManager::exp); 39 | case EaseOutExpo -> out(EasingManager::exp); 40 | case EaseInOutExpo -> inOut(EasingManager::exp); 41 | case EaseInCirc -> in(EasingManager::circle); 42 | case EaseOutCirc -> out(EasingManager::circle); 43 | case EaseInOutCirc -> inOut(EasingManager::circle); 44 | case EaseInQuart -> in(quart); 45 | case EaseOutQuart -> out(quart); 46 | case EaseInOutQuart -> inOut(quart); 47 | case EaseInQuint -> in(quint); 48 | case EaseOutQuint -> out(quint); 49 | case EaseInOutQuint -> inOut(quint); 50 | case EaseInBack -> in(back(args.arg0())); 51 | case EaseOutBack -> out(back(args.arg0())); 52 | case EaseInOutBack -> inOut(back(args.arg0())); 53 | case EaseInElastic -> in(elastic(args.arg0())); 54 | case EaseOutElastic -> out(elastic(args.arg0())); 55 | case EaseInOutElastic -> inOut(elastic(args.arg0())); 56 | case EaseInBounce -> in(bounce(args.arg0())); 57 | case EaseOutBounce -> out(bounce(args.arg0())); 58 | case EaseInOutBounce -> inOut(bounce(args.arg0())); 59 | }; 60 | } 61 | 62 | // The MIT license notice below applies to the easing functions below except for 63 | // bounce and step 64 | /** 65 | * Copyright (c) Facebook, Inc. and its affiliates. 66 | * 67 | * This source code is licensed under the MIT license found in the LICENSE file 68 | * in the root directory of this source tree. 69 | */ 70 | 71 | /** 72 | * Runs an easing function forwards. 73 | */ 74 | static Double2DoubleFunction in(Double2DoubleFunction easing) { 75 | return easing; 76 | } 77 | 78 | /** 79 | * Runs an easing function backwards. 80 | */ 81 | static Double2DoubleFunction out(Double2DoubleFunction easing) { 82 | return t -> 1 - easing.apply(1 - t); 83 | } 84 | 85 | /** 86 | * Makes any easing function symmetrical. The easing function will run forwards 87 | * for half of the duration, then backwards for the rest of the duration. 88 | */ 89 | static Double2DoubleFunction inOut(Double2DoubleFunction easing) { 90 | return t -> { 91 | if (t < 0.5) { 92 | return easing.apply(t * 2) / 2; 93 | } 94 | return 1 - easing.apply((1 - t) * 2) / 2; 95 | }; 96 | } 97 | 98 | /** 99 | * A stepping function, returns 1 for any positive value of `n`. 100 | */ 101 | static Double2DoubleFunction step0() { 102 | return n -> n > 0 ? 1D : 0; 103 | } 104 | 105 | /** 106 | * A stepping function, returns 1 if `n` is greater than or equal to 1. 107 | */ 108 | static Double2DoubleFunction step1() { 109 | return n -> n >= 1D ? 1D : 0; 110 | } 111 | 112 | /** 113 | * A linear function, `f(t) = t`. Position correlates to elapsed time one to 114 | * one. 115 | *

116 | * http://cubic-bezier.com/#0,0,1,1 117 | */ 118 | static double linear(double t) { 119 | return t; 120 | } 121 | 122 | /** 123 | * A simple inertial interaction, similar to an object slowly accelerating to 124 | * speed. 125 | * 126 | * http://cubic-bezier.com/#.42,0,1,1 127 | **/ 128 | // static ease(t) { 129 | // if (!ease) { 130 | // ease = Easing.bezier(0.42, 0, 1, 1); 131 | // } 132 | // return ease(t); 133 | // } 134 | 135 | /** 136 | * A quadratic function, `f(t) = t * t`. Position equals the square of elapsed 137 | * time. 138 | *

139 | * http://easings.net/#easeInQuad 140 | */ 141 | static double quad(double t) { 142 | return t * t; 143 | } 144 | 145 | /** 146 | * A cubic function, `f(t) = t * t * t`. Position equals the cube of elapsed 147 | * time. 148 | *

149 | * http://easings.net/#easeInCubic 150 | */ 151 | static double cubic(double t) { 152 | return t * t * t; 153 | } 154 | 155 | /** 156 | * A power function. Position is equal to the Nth power of elapsed time. 157 | *

158 | * n = 4: http://easings.net/#easeInQuart n = 5: http://easings.net/#easeInQuint 159 | */ 160 | static Double2DoubleFunction poly(double n) { 161 | return (t) -> Math.pow(t, n); 162 | } 163 | 164 | /** 165 | * A sinusoidal function. 166 | *

167 | * http://easings.net/#easeInSine 168 | */ 169 | static double sin(double t) { 170 | return 1 - Math.cos((float) ((t * Math.PI) / 2)); 171 | } 172 | 173 | /** 174 | * A circular function. 175 | *

176 | * http://easings.net/#easeInCirc 177 | */ 178 | static double circle(double t) { 179 | return 1 - Math.sqrt(1 - t * t); 180 | } 181 | 182 | /** 183 | * An exponential function. 184 | *

185 | * http://easings.net/#easeInExpo 186 | */ 187 | static double exp(double t) { 188 | return Math.pow(2, 10 * (t - 1)); 189 | } 190 | 191 | /** 192 | * A simple elastic interaction, similar to a spring oscillating back and forth. 193 | *

194 | * Default bounciness is 1, which overshoots a little bit once. 0 bounciness 195 | * doesn't overshoot at all, and bounciness of N > 1 will overshoot about N 196 | * times. 197 | *

198 | * http://easings.net/#easeInElastic 199 | */ 200 | static Double2DoubleFunction elastic(Double bounciness) { 201 | double p = (bounciness == null ? 1 : bounciness) * Math.PI; 202 | return t -> 1 - Math.pow(Math.cos((float) ((t * Math.PI) / 2)), 3) * Math.cos((float) (t * p)); 203 | } 204 | 205 | /** 206 | * Use with `Animated.parallel()` to create a simple effect where the object 207 | * animates back slightly as the animation starts. 208 | *

209 | * Wolfram Plot: 210 | *

211 | * - http://tiny.cc/back_default (s = 1.70158, default) 212 | */ 213 | static Double2DoubleFunction back(Double s) { 214 | double p = s == null ? 1.70158 : s * 1.70158; 215 | return t -> t * t * ((p + 1) * t - p); 216 | } 217 | 218 | /** 219 | * Provides a simple bouncing effect. 220 | *

221 | * Props to Waterded#6455 for making the bounce adjustable and GiantLuigi4#6616 222 | * for helping clean it up using min instead of ternaries 223 | * http://easings.net/#easeInBounce 224 | */ 225 | public static Double2DoubleFunction bounce(Double s) { 226 | double k = s == null ? 0.5 : s; 227 | Double2DoubleFunction q = x -> (121.0 / 16.0) * x * x; 228 | Double2DoubleFunction w = x -> ((121.0 / 4.0) * k) * Math.pow(x - (6.0 / 11.0), 2) + 1 - k; 229 | Double2DoubleFunction r = x -> 121 * k * k * Math.pow(x - (9.0 / 11.0), 2) + 1 - k * k; 230 | Double2DoubleFunction t = x -> 484 * k * k * k * Math.pow(x - (10.5 / 11.0), 2) + 1 - k * k * k; 231 | return x -> min(q.apply(x), w.apply(x), r.apply(x), t.apply(x)); 232 | } 233 | 234 | static Double2DoubleFunction step(Double stepArg) { 235 | int steps = stepArg != null ? stepArg.intValue() : 2; 236 | double[] intervals = stepRange(steps); 237 | return t -> intervals[findIntervalBorderIndex(t, intervals, false)]; 238 | } 239 | 240 | static double min(double a, double b, double c, double d) { 241 | return Math.min(Math.min(a, b), Math.min(c, d)); 242 | } 243 | 244 | // The MIT license notice below applies to the function findIntervalBorderIndex 245 | /* 246 | * The MIT License (MIT) 247 | * 248 | * Copyright (c) 2015 Boris Chumichev 249 | * 250 | * Permission is hereby granted, free of charge, to any person obtaining a copy 251 | * of this software and associated documentation files (the "Software"), to deal 252 | * in the Software without restriction, including without limitation the rights 253 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 254 | * copies of the Software, and to permit persons to whom the Software is 255 | * furnished to do so, subject to the following conditions: 256 | * 257 | * The above copyright notice and this permission notice shall be included in 258 | * all copies or substantial portions of the Software. 259 | * 260 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 261 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 262 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 263 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 264 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 265 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 266 | * SOFTWARE. 267 | * 268 | * /** 269 | * 270 | * Utilizes bisection method to search an interval to which point belongs to, 271 | * then returns an index of left or right border of the interval 272 | * 273 | * @param {Number} point 274 | * 275 | * @param {Array} intervals 276 | * 277 | * @param {Boolean} useRightBorder 278 | * 279 | * @returns {Number} 280 | */ 281 | static int findIntervalBorderIndex(double point, double[] intervals, boolean useRightBorder) { 282 | // If point is beyond given intervals 283 | if (point < intervals[0]) 284 | return 0; 285 | if (point > intervals[intervals.length - 1]) 286 | return intervals.length - 1; 287 | // If point is inside interval 288 | // Start searching on a full range of intervals 289 | int indexOfNumberToCompare = 0; 290 | int leftBorderIndex = 0; 291 | int rightBorderIndex = intervals.length - 1; 292 | // Reduce searching range till it find an interval point belongs to using binary 293 | // search 294 | while (rightBorderIndex - leftBorderIndex != 1) { 295 | indexOfNumberToCompare = leftBorderIndex + (rightBorderIndex - leftBorderIndex) / 2; 296 | if (point >= intervals[indexOfNumberToCompare]) { 297 | leftBorderIndex = indexOfNumberToCompare; 298 | } else { 299 | rightBorderIndex = indexOfNumberToCompare; 300 | } 301 | } 302 | return useRightBorder ? rightBorderIndex : leftBorderIndex; 303 | } 304 | 305 | static double[] stepRange(int steps) { 306 | final double stop = 1; 307 | if (steps < 2) 308 | throw new IllegalArgumentException("steps must be > 2, got:" + steps); 309 | double stepLength = stop / (double) steps; 310 | double[] stepArray = new double[steps]; 311 | 312 | for (int i = 0; i < steps; i++) { 313 | stepArray[i] = i * stepLength; 314 | } 315 | 316 | return stepArray; 317 | }; 318 | } 319 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/processor/AnimationProcessor.java: -------------------------------------------------------------------------------- 1 | package software.bernie.geckolib3.core.processor; 2 | 3 | import it.unimi.dsi.fastutil.ints.IntOpenHashSet; 4 | import it.unimi.dsi.fastutil.ints.IntSet; 5 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 6 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 7 | import org.apache.commons.lang3.tuple.Pair; 8 | import software.bernie.geckolib3.core.IAnimatable; 9 | import software.bernie.geckolib3.core.IAnimatableModel; 10 | import software.bernie.geckolib3.core.controller.AnimationController; 11 | import software.bernie.geckolib3.core.event.predicate.AnimationEvent; 12 | import software.bernie.geckolib3.core.keyframe.AnimationPoint; 13 | import software.bernie.geckolib3.core.keyframe.BoneAnimationQueue; 14 | import software.bernie.geckolib3.core.manager.AnimationData; 15 | import software.bernie.geckolib3.core.molang.MolangParser; 16 | import software.bernie.geckolib3.core.snapshot.BoneSnapshot; 17 | import software.bernie.geckolib3.core.snapshot.DirtyTracker; 18 | import software.bernie.geckolib3.core.util.MathUtil; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | public class AnimationProcessor { 24 | public boolean reloadAnimations = false; 25 | private final List modelRendererList = new ObjectArrayList<>(); 26 | private double lastTickValue = -1; 27 | private final IntSet animatedEntities = new IntOpenHashSet(); 28 | private final IAnimatableModel animatedModel; 29 | 30 | public AnimationProcessor(IAnimatableModel animatedModel) { 31 | this.animatedModel = animatedModel; 32 | } 33 | 34 | public void tickAnimation(IAnimatable entity, int uniqueID, double seekTime, AnimationEvent event, 35 | MolangParser parser, boolean crashWhenCantFindBone) { 36 | if (seekTime != lastTickValue) { 37 | animatedEntities.clear(); 38 | } else if (animatedEntities.contains(uniqueID)) { // Entity already animated on this tick 39 | return; 40 | } 41 | 42 | lastTickValue = seekTime; 43 | animatedEntities.add(uniqueID); 44 | 45 | // Each animation has its own collection of animations (called the 46 | // EntityAnimationManager), which allows for multiple independent animations 47 | AnimationData manager = entity.getFactory().getOrCreateAnimationData(uniqueID); 48 | // Keeps track of which bones have had animations applied to them, and 49 | // eventually sets the ones that don't have an animation to their default values 50 | Map modelTracker = createNewDirtyTracker(); 51 | 52 | // Store the current value of each bone rotation/position/scale 53 | updateBoneSnapshots(manager.getBoneSnapshotCollection()); 54 | 55 | Map> boneSnapshots = manager.getBoneSnapshotCollection(); 56 | 57 | for (AnimationController controller : manager.getAnimationControllers().values()) { 58 | if (reloadAnimations) { 59 | controller.markNeedsReload(); 60 | controller.getBoneAnimationQueues().clear(); 61 | } 62 | 63 | controller.isJustStarting = manager.isFirstTick; 64 | 65 | // Set current controller to animation test event 66 | event.setController(controller); 67 | 68 | // Process animations and add new values to the point queues 69 | controller.process(seekTime, event, modelRendererList, boneSnapshots, parser, crashWhenCantFindBone); 70 | 71 | // Loop through every single bone and lerp each property 72 | for (BoneAnimationQueue boneAnimation : controller.getBoneAnimationQueues().values()) { 73 | IBone bone = boneAnimation.bone(); 74 | BoneSnapshot snapshot = boneSnapshots.get(bone.getName()).getRight(); 75 | BoneSnapshot initialSnapshot = bone.getInitialSnapshot(); 76 | 77 | AnimationPoint rXPoint = boneAnimation.rotationXQueue().poll(); 78 | AnimationPoint rYPoint = boneAnimation.rotationYQueue().poll(); 79 | AnimationPoint rZPoint = boneAnimation.rotationZQueue().poll(); 80 | 81 | AnimationPoint pXPoint = boneAnimation.positionXQueue().poll(); 82 | AnimationPoint pYPoint = boneAnimation.positionYQueue().poll(); 83 | AnimationPoint pZPoint = boneAnimation.positionZQueue().poll(); 84 | 85 | AnimationPoint sXPoint = boneAnimation.scaleXQueue().poll(); 86 | AnimationPoint sYPoint = boneAnimation.scaleYQueue().poll(); 87 | AnimationPoint sZPoint = boneAnimation.scaleZQueue().poll(); 88 | 89 | // If there's any rotation points for this bone 90 | DirtyTracker dirtyTracker = modelTracker.get(bone.getName()); 91 | if (dirtyTracker == null) { 92 | continue; 93 | } 94 | if (rXPoint != null && rYPoint != null && rZPoint != null) { 95 | bone.setRotationX(MathUtil.lerpValues(rXPoint, controller.easingType, controller.customEasingMethod) 96 | + initialSnapshot.rotationValueX); 97 | bone.setRotationY(MathUtil.lerpValues(rYPoint, controller.easingType, controller.customEasingMethod) 98 | + initialSnapshot.rotationValueY); 99 | bone.setRotationZ(MathUtil.lerpValues(rZPoint, controller.easingType, controller.customEasingMethod) 100 | + initialSnapshot.rotationValueZ); 101 | snapshot.rotationValueX = bone.getRotationX(); 102 | snapshot.rotationValueY = bone.getRotationY(); 103 | snapshot.rotationValueZ = bone.getRotationZ(); 104 | snapshot.isCurrentlyRunningRotationAnimation = true; 105 | dirtyTracker.hasRotationChanged = true; 106 | } 107 | 108 | // If there's any position points for this bone 109 | if (pXPoint != null && pYPoint != null && pZPoint != null) { 110 | bone.setPositionX( 111 | MathUtil.lerpValues(pXPoint, controller.easingType, controller.customEasingMethod)); 112 | bone.setPositionY( 113 | MathUtil.lerpValues(pYPoint, controller.easingType, controller.customEasingMethod)); 114 | bone.setPositionZ( 115 | MathUtil.lerpValues(pZPoint, controller.easingType, controller.customEasingMethod)); 116 | snapshot.positionOffsetX = bone.getPositionX(); 117 | snapshot.positionOffsetY = bone.getPositionY(); 118 | snapshot.positionOffsetZ = bone.getPositionZ(); 119 | snapshot.isCurrentlyRunningPositionAnimation = true; 120 | 121 | dirtyTracker.hasPositionChanged = true; 122 | } 123 | 124 | // If there's any scale points for this bone 125 | if (sXPoint != null && sYPoint != null && sZPoint != null) { 126 | bone.setScaleX(MathUtil.lerpValues(sXPoint, controller.easingType, controller.customEasingMethod)); 127 | bone.setScaleY(MathUtil.lerpValues(sYPoint, controller.easingType, controller.customEasingMethod)); 128 | bone.setScaleZ(MathUtil.lerpValues(sZPoint, controller.easingType, controller.customEasingMethod)); 129 | snapshot.scaleValueX = bone.getScaleX(); 130 | snapshot.scaleValueY = bone.getScaleY(); 131 | snapshot.scaleValueZ = bone.getScaleZ(); 132 | snapshot.isCurrentlyRunningScaleAnimation = true; 133 | 134 | dirtyTracker.hasScaleChanged = true; 135 | } 136 | } 137 | } 138 | 139 | this.reloadAnimations = false; 140 | 141 | double resetTickLength = manager.getResetSpeed(); 142 | for (Map.Entry tracker : modelTracker.entrySet()) { 143 | IBone model = tracker.getValue().model; 144 | BoneSnapshot initialSnapshot = model.getInitialSnapshot(); 145 | BoneSnapshot saveSnapshot = boneSnapshots.get(tracker.getKey()).getRight(); 146 | if (saveSnapshot == null) { 147 | if (crashWhenCantFindBone) { 148 | throw new RuntimeException( 149 | "Could not find save snapshot for bone: " + tracker.getValue().model.getName() 150 | + ". Please don't add bones that are used in an animation at runtime."); 151 | } else { 152 | continue; 153 | } 154 | } 155 | 156 | if (!tracker.getValue().hasRotationChanged) { 157 | if (saveSnapshot.isCurrentlyRunningRotationAnimation) { 158 | saveSnapshot.mostRecentResetRotationTick = (float) seekTime; 159 | saveSnapshot.isCurrentlyRunningRotationAnimation = false; 160 | } 161 | 162 | double percentageReset = Math 163 | .min((seekTime - saveSnapshot.mostRecentResetRotationTick) / resetTickLength, 1); 164 | 165 | model.setRotationX(MathUtil.lerpValues(percentageReset, saveSnapshot.rotationValueX, 166 | initialSnapshot.rotationValueX)); 167 | model.setRotationY(MathUtil.lerpValues(percentageReset, saveSnapshot.rotationValueY, 168 | initialSnapshot.rotationValueY)); 169 | model.setRotationZ(MathUtil.lerpValues(percentageReset, saveSnapshot.rotationValueZ, 170 | initialSnapshot.rotationValueZ)); 171 | 172 | if (percentageReset >= 1) { 173 | saveSnapshot.rotationValueX = model.getRotationX(); 174 | saveSnapshot.rotationValueY = model.getRotationY(); 175 | saveSnapshot.rotationValueZ = model.getRotationZ(); 176 | } 177 | } 178 | if (!tracker.getValue().hasPositionChanged) { 179 | if (saveSnapshot.isCurrentlyRunningPositionAnimation) { 180 | saveSnapshot.mostRecentResetPositionTick = (float) seekTime; 181 | saveSnapshot.isCurrentlyRunningPositionAnimation = false; 182 | } 183 | 184 | double percentageReset = Math 185 | .min((seekTime - saveSnapshot.mostRecentResetPositionTick) / resetTickLength, 1); 186 | 187 | model.setPositionX(MathUtil.lerpValues(percentageReset, saveSnapshot.positionOffsetX, 188 | initialSnapshot.positionOffsetX)); 189 | model.setPositionY(MathUtil.lerpValues(percentageReset, saveSnapshot.positionOffsetY, 190 | initialSnapshot.positionOffsetY)); 191 | model.setPositionZ(MathUtil.lerpValues(percentageReset, saveSnapshot.positionOffsetZ, 192 | initialSnapshot.positionOffsetZ)); 193 | 194 | if (percentageReset >= 1) { 195 | saveSnapshot.positionOffsetX = model.getPositionX(); 196 | saveSnapshot.positionOffsetY = model.getPositionY(); 197 | saveSnapshot.positionOffsetZ = model.getPositionZ(); 198 | } 199 | } 200 | if (!tracker.getValue().hasScaleChanged) { 201 | if (saveSnapshot.isCurrentlyRunningScaleAnimation) { 202 | saveSnapshot.mostRecentResetScaleTick = (float) seekTime; 203 | saveSnapshot.isCurrentlyRunningScaleAnimation = false; 204 | } 205 | 206 | double percentageReset = Math.min((seekTime - saveSnapshot.mostRecentResetScaleTick) / resetTickLength, 207 | 1); 208 | 209 | model.setScaleX( 210 | MathUtil.lerpValues(percentageReset, saveSnapshot.scaleValueX, initialSnapshot.scaleValueX)); 211 | model.setScaleY( 212 | MathUtil.lerpValues(percentageReset, saveSnapshot.scaleValueY, initialSnapshot.scaleValueY)); 213 | model.setScaleZ( 214 | MathUtil.lerpValues(percentageReset, saveSnapshot.scaleValueZ, initialSnapshot.scaleValueZ)); 215 | 216 | if (percentageReset >= 1) { 217 | saveSnapshot.scaleValueX = model.getScaleX(); 218 | saveSnapshot.scaleValueY = model.getScaleY(); 219 | saveSnapshot.scaleValueZ = model.getScaleZ(); 220 | } 221 | } 222 | } 223 | manager.isFirstTick = false; 224 | } 225 | 226 | private Map createNewDirtyTracker() { 227 | Map tracker = new Object2ObjectOpenHashMap<>(); 228 | for (IBone bone : modelRendererList) { 229 | tracker.put(bone.getName(), new DirtyTracker(false, false, false, bone)); 230 | } 231 | return tracker; 232 | } 233 | 234 | private void updateBoneSnapshots(Map> boneSnapshotCollection) { 235 | for (IBone bone : modelRendererList) { 236 | if (!boneSnapshotCollection.containsKey(bone.getName())) { 237 | boneSnapshotCollection.put(bone.getName(), Pair.of(bone, new BoneSnapshot(bone.getInitialSnapshot()))); 238 | } 239 | } 240 | } 241 | 242 | /** 243 | * Gets a bone by name. 244 | * 245 | * @param boneName The bone name 246 | * @return the bone 247 | */ 248 | public IBone getBone(String boneName) { 249 | for (IBone bone : this.modelRendererList) { 250 | if (bone.getName().equals(boneName)) 251 | return bone; 252 | } 253 | 254 | return null; 255 | } 256 | 257 | /** 258 | * Register model renderer. Each AnimatedModelRenderer (group in blockbench) 259 | * NEEDS to be registered via this method. 260 | * 261 | * @param modelRenderer The model renderer 262 | */ 263 | public void registerModelRenderer(IBone modelRenderer) { 264 | modelRenderer.saveInitialSnapshot(); 265 | modelRendererList.add(modelRenderer); 266 | } 267 | 268 | public void clearModelRendererList() { 269 | this.modelRendererList.clear(); 270 | } 271 | 272 | public List getModelRendererList() { 273 | return modelRendererList; 274 | } 275 | 276 | public void preAnimationSetup(IAnimatable animatable, double seekTime) { 277 | this.animatedModel.setMolangQueries(animatable, seekTime); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/main/java/software/bernie/geckolib3/core/controller/AnimationController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. 3 | * Author: Bernie G. (Gecko) 4 | */ 5 | 6 | package software.bernie.geckolib3.core.controller; 7 | 8 | import java.util.HashMap; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | import java.util.Optional; 14 | import java.util.Queue; 15 | import java.util.Set; 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | import java.util.function.Function; 18 | import java.util.stream.Collectors; 19 | 20 | import org.apache.commons.lang3.tuple.Pair; 21 | 22 | import com.eliotlash.mclib.math.IValue; 23 | 24 | import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; 25 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 26 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 27 | import software.bernie.geckolib3.core.AnimationState; 28 | import software.bernie.geckolib3.core.ConstantValue; 29 | import software.bernie.geckolib3.core.IAnimatable; 30 | import software.bernie.geckolib3.core.IAnimatableModel; 31 | import software.bernie.geckolib3.core.PlayState; 32 | import software.bernie.geckolib3.core.builder.Animation; 33 | import software.bernie.geckolib3.core.builder.AnimationBuilder; 34 | import software.bernie.geckolib3.core.builder.ILoopType; 35 | import software.bernie.geckolib3.core.easing.EasingType; 36 | import software.bernie.geckolib3.core.event.CustomInstructionKeyframeEvent; 37 | import software.bernie.geckolib3.core.event.ParticleKeyFrameEvent; 38 | import software.bernie.geckolib3.core.event.SoundKeyframeEvent; 39 | import software.bernie.geckolib3.core.event.predicate.AnimationEvent; 40 | import software.bernie.geckolib3.core.keyframe.AnimationPoint; 41 | import software.bernie.geckolib3.core.keyframe.BoneAnimation; 42 | import software.bernie.geckolib3.core.keyframe.BoneAnimationQueue; 43 | import software.bernie.geckolib3.core.keyframe.EventKeyFrame; 44 | import software.bernie.geckolib3.core.keyframe.KeyFrame; 45 | import software.bernie.geckolib3.core.keyframe.KeyFrameLocation; 46 | import software.bernie.geckolib3.core.keyframe.ParticleEventKeyFrame; 47 | import software.bernie.geckolib3.core.keyframe.VectorKeyFrameList; 48 | import software.bernie.geckolib3.core.molang.MolangParser; 49 | import software.bernie.geckolib3.core.processor.IBone; 50 | import software.bernie.geckolib3.core.snapshot.BoneSnapshot; 51 | import software.bernie.geckolib3.core.util.Axis; 52 | 53 | /** 54 | * The type Animation controller. 55 | * 56 | * @param the type parameter 57 | */ 58 | public class AnimationController { 59 | static List> modelFetchers = new ObjectArrayList<>(); 60 | /** 61 | * The Entity. 62 | */ 63 | protected T animatable; 64 | /** 65 | * The animation predicate, is tested in every process call (i.e. every frame) 66 | */ 67 | protected IAnimationPredicate animationPredicate; 68 | 69 | /** 70 | * The name of the animation controller 71 | */ 72 | private final String name; 73 | 74 | protected AnimationState animationState = AnimationState.Stopped; 75 | 76 | /** 77 | * How long it takes to transition between animations 78 | */ 79 | public double transitionLengthTicks; 80 | 81 | /** 82 | * The sound listener is called every time a sound keyframe is encountered (i.e. 83 | * every frame) 84 | */ 85 | private ISoundListener soundListener; 86 | 87 | /** 88 | * The particle listener is called every time a particle keyframe is encountered 89 | * (i.e. every frame) 90 | */ 91 | private IParticleListener particleListener; 92 | 93 | /** 94 | * The custom instruction listener is called every time a custom instruction 95 | * keyframe is encountered (i.e. every frame) 96 | */ 97 | private ICustomInstructionListener customInstructionListener; 98 | 99 | public boolean isJustStarting = false; 100 | 101 | public static void addModelFetcher(ModelFetcher fetcher) { 102 | modelFetchers.add(fetcher); 103 | } 104 | 105 | public static void removeModelFetcher(ModelFetcher fetcher) { 106 | Objects.requireNonNull(fetcher); 107 | modelFetchers.remove(fetcher); 108 | } 109 | 110 | /** 111 | * An AnimationPredicate is run every render frame for ever AnimationController. 112 | * The "test" method is where you should change animations, stop animations, 113 | * restart, etc. 114 | */ 115 | @FunctionalInterface 116 | public interface IAnimationPredicate

{ 117 | /** 118 | * An AnimationPredicate is run every render frame for ever AnimationController. 119 | * The "test" method is where you should change animations, stop animations, 120 | * restart, etc. 121 | * 122 | * @return CONTINUE if the animation should continue, STOP if it should stop. 123 | */ 124 | PlayState test(AnimationEvent

event); 125 | } 126 | 127 | /** 128 | * Sound Listeners are run when a sound keyframe is hit. You can either return 129 | * the SoundEvent and geckolib will play the sound for you, or return null and 130 | * handle the sounds yourself. 131 | */ 132 | @FunctionalInterface 133 | public interface ISoundListener { 134 | /** 135 | * Sound Listeners are run when a sound keyframe is hit. You can either return 136 | * the SoundEvent and geckolib will play the sound for you, or return null and 137 | * handle the sounds yourself. 138 | */ 139 | void playSound(SoundKeyframeEvent event); 140 | } 141 | 142 | /** 143 | * Particle Listeners are run when a sound keyframe is hit. You need to handle 144 | * the actual playing of the particle yourself. 145 | */ 146 | @FunctionalInterface 147 | public interface IParticleListener { 148 | /** 149 | * Particle Listeners are run when a sound keyframe is hit. You need to handle 150 | * the actual playing of the particle yourself. 151 | */ 152 | void summonParticle(ParticleKeyFrameEvent event); 153 | } 154 | 155 | /** 156 | * Custom instructions can be added in blockbench by enabling animation effects 157 | * in Animation - Animate Effects. You can then add custom instruction keyframes 158 | * and use them as timecodes/events to handle in code. 159 | */ 160 | @FunctionalInterface 161 | public interface ICustomInstructionListener { 162 | /** 163 | * Custom instructions can be added in blockbench by enabling animation effects 164 | * in Animation - Animate Effects. You can then add custom instruction keyframes 165 | * and use them as timecodes/events to handle in code. 166 | */ 167 | void executeInstruction(CustomInstructionKeyframeEvent event); 168 | } 169 | 170 | private final HashMap boneAnimationQueues = new HashMap<>(); 171 | public double tickOffset; 172 | protected Queue animationQueue = new LinkedList<>(); 173 | protected Animation currentAnimation; 174 | protected AnimationBuilder currentAnimationBuilder = new AnimationBuilder(); 175 | protected boolean shouldResetTick = false; 176 | private final HashMap boneSnapshots = new HashMap<>(); 177 | private boolean justStopped = false; 178 | protected boolean justStartedTransition = false; 179 | public Double2DoubleFunction customEasingMethod; 180 | protected boolean needsAnimationReload = false; 181 | public double animationSpeed = 1D; 182 | private final Set> executedKeyFrames = new ObjectOpenHashSet<>(); 183 | 184 | /** 185 | * This method sets the current animation with an animation builder. You can run 186 | * this method every frame, if you pass in the same animation builder every 187 | * time, it won't restart. Additionally, it smoothly transitions between 188 | * animation states. 189 | */ 190 | public void setAnimation(AnimationBuilder builder) { 191 | IAnimatableModel model = getModel(this.animatable); 192 | if (model != null) { 193 | if (builder == null || builder.getRawAnimationList().size() == 0) { 194 | this.animationState = AnimationState.Stopped; 195 | } 196 | else if (!builder.getRawAnimationList().equals(this.currentAnimationBuilder.getRawAnimationList()) 197 | || this.needsAnimationReload) { 198 | AtomicBoolean encounteredError = new AtomicBoolean(false); 199 | // Convert the list of animation names to the actual list, keeping track of the 200 | // loop boolean along the way 201 | LinkedList animations = builder.getRawAnimationList().stream().map((rawAnimation) -> { 202 | Animation animation = model.getAnimation(rawAnimation.animationName, animatable); 203 | 204 | if (animation == null) { 205 | System.out.printf("Could not load animation: %s. Is it missing?", rawAnimation.animationName); 206 | encounteredError.set(true); 207 | } 208 | 209 | if (animation != null && rawAnimation.loopType != null) 210 | animation.loop = rawAnimation.loopType; 211 | 212 | return animation; 213 | }).collect(Collectors.toCollection(LinkedList::new)); 214 | 215 | if (encounteredError.get()) 216 | return; 217 | 218 | this.animationQueue = animations; 219 | this.currentAnimationBuilder = builder; 220 | this.shouldResetTick = true; // Reset the adjusted tick to 0 on next animation process call 221 | this.animationState = AnimationState.Transitioning; 222 | this.justStartedTransition = true; 223 | this.needsAnimationReload = false; 224 | } 225 | } 226 | } 227 | 228 | /** 229 | * By default Geckolib uses the easing types of every keyframe. If you want to 230 | * override that for an entire AnimationController, change this value. 231 | */ 232 | public EasingType easingType = EasingType.NONE; 233 | 234 | /** 235 | * Instantiates a new Animation controller. Each animation controller can run 236 | * one animation at a time. You can have several animation controllers for each 237 | * entity, i.e. one animation to control the entity's size, one to control 238 | * movement, attacks, etc. 239 | * 240 | * @param animatable The entity 241 | * @param name Name of the animation controller 242 | * (move_controller, size_controller, 243 | * attack_controller, etc.) 244 | * @param transitionLengthTicks How long it takes to transition between 245 | * animations (IN TICKS!!) 246 | */ 247 | public AnimationController(T animatable, String name, float transitionLengthTicks, 248 | IAnimationPredicate animationPredicate) { 249 | this.animatable = animatable; 250 | this.name = name; 251 | this.transitionLengthTicks = transitionLengthTicks; 252 | this.animationPredicate = animationPredicate; 253 | this.tickOffset = 0.0d; 254 | } 255 | 256 | /** 257 | * Instantiates a new Animation controller. Each animation controller can run 258 | * one animation at a time. You can have several animation controllers for each 259 | * entity, i.e. one animation to control the entity's size, one to control 260 | * movement, attacks, etc. 261 | * 262 | * @param animatable The entity 263 | * @param name Name of the animation controller 264 | * (move_controller, size_controller, 265 | * attack_controller, etc.) 266 | * @param transitionLengthTicks How long it takes to transition between 267 | * animations (IN TICKS!!) 268 | * @param easingtype The method of easing to use. The other 269 | * constructor defaults to no easing. 270 | */ 271 | public AnimationController(T animatable, String name, float transitionLengthTicks, EasingType easingtype, 272 | IAnimationPredicate animationPredicate) { 273 | this.animatable = animatable; 274 | this.name = name; 275 | this.transitionLengthTicks = transitionLengthTicks; 276 | this.easingType = easingtype; 277 | this.animationPredicate = animationPredicate; 278 | this.tickOffset = 0.0d; 279 | } 280 | 281 | /** 282 | * Instantiates a new Animation controller. Each animation controller can run 283 | * one animation at a time. You can have several animation controllers for each 284 | * entity, i.e. one animation to control the entity's size, one to control 285 | * movement, attacks, etc. 286 | * 287 | * @param animatable The entity 288 | * @param name Name of the animation controller 289 | * (move_controller, size_controller, 290 | * attack_controller, etc.) 291 | * @param transitionLengthTicks How long it takes to transition between 292 | * animations (IN TICKS!!) 293 | * @param customEasingMethod If you want to use an easing method that's not 294 | * included in the EasingType enum, pass your 295 | * method into here. The parameter that's passed in 296 | * will be a number between 0 and 1. Return a 297 | * number also within 0 and 1. Take a look at 298 | * {@link software.bernie.geckolib3.core.easing.EasingManager} 299 | */ 300 | public AnimationController(T animatable, String name, float transitionLengthTicks, 301 | Double2DoubleFunction customEasingMethod, IAnimationPredicate animationPredicate) { 302 | this.animatable = animatable; 303 | this.name = name; 304 | this.transitionLengthTicks = transitionLengthTicks; 305 | this.customEasingMethod = customEasingMethod; 306 | this.easingType = EasingType.CUSTOM; 307 | this.animationPredicate = animationPredicate; 308 | this.tickOffset = 0.0d; 309 | } 310 | 311 | /** 312 | * Gets the controller's name. 313 | * 314 | * @return the name 315 | */ 316 | public String getName() { 317 | return this.name; 318 | } 319 | 320 | /** 321 | * Gets the current animation. Can be null 322 | * 323 | * @return the current animation 324 | */ 325 | 326 | public Animation getCurrentAnimation() { 327 | return this.currentAnimation; 328 | } 329 | 330 | /** 331 | * Returns the current state of this animation controller. 332 | */ 333 | public AnimationState getAnimationState() { 334 | return this.animationState; 335 | } 336 | 337 | /** 338 | * Gets the current animation's bone animation queues. 339 | * 340 | * @return the bone animation queues 341 | */ 342 | public HashMap getBoneAnimationQueues() { 343 | return this.boneAnimationQueues; 344 | } 345 | 346 | /** 347 | * Registers a sound listener. 348 | */ 349 | public void registerSoundListener(ISoundListener soundListener) { 350 | this.soundListener = soundListener; 351 | } 352 | 353 | /** 354 | * Registers a particle listener. 355 | */ 356 | public void registerParticleListener(IParticleListener particleListener) { 357 | this.particleListener = particleListener; 358 | } 359 | 360 | /** 361 | * Registers a custom instruction listener. 362 | */ 363 | public void registerCustomInstructionListener(ICustomInstructionListener customInstructionListener) { 364 | this.customInstructionListener = customInstructionListener; 365 | } 366 | 367 | /** 368 | * This method is called every frame in order to populate the animation point 369 | * queues, and process animation state logic. 370 | * 371 | * @param tick The current tick + partial tick 372 | * @param event The animation test event 373 | * @param modelRendererList The list of all AnimatedModelRender's 374 | * @param boneSnapshotCollection The bone snapshot collection 375 | */ 376 | public void process(final double tick, AnimationEvent event, List modelRendererList, 377 | Map> boneSnapshotCollection, MolangParser parser, 378 | boolean crashWhenCantFindBone) { 379 | parser.setValue("query.life_time", () -> tick / 20); 380 | 381 | if (this.currentAnimation != null) { 382 | IAnimatableModel model = getModel(this.animatable); 383 | 384 | if (model != null) { 385 | Animation animation = model.getAnimation(currentAnimation.animationName, this.animatable); 386 | 387 | if (animation != null) { 388 | ILoopType loop = this.currentAnimation.loop; 389 | this.currentAnimation = animation; 390 | this.currentAnimation.loop = loop; 391 | } 392 | } 393 | } 394 | 395 | createInitialQueues(modelRendererList); 396 | 397 | double adjustedTick = adjustTick(tick); 398 | 399 | // Transition period has ended, reset the tick and set the animation to running 400 | if (animationState == AnimationState.Transitioning && adjustedTick >= this.transitionLengthTicks) { 401 | this.shouldResetTick = true; 402 | this.animationState = AnimationState.Running; 403 | adjustedTick = adjustTick(tick); 404 | } 405 | 406 | assert adjustedTick >= 0 : "GeckoLib: Tick was less than zero"; 407 | 408 | // This tests the animation predicate 409 | PlayState playState = this.testAnimationPredicate(event); 410 | 411 | if (playState == PlayState.STOP || (this.currentAnimation == null && this.animationQueue.size() == 0)) { 412 | // The animation should transition to the model's initial state 413 | this.animationState = AnimationState.Stopped; 414 | this.justStopped = true; 415 | 416 | return; 417 | } 418 | 419 | if (this.justStartedTransition && (this.shouldResetTick || this.justStopped)) { 420 | this.justStopped = false; 421 | adjustedTick = adjustTick(tick); 422 | } 423 | else if (this.currentAnimation == null && this.animationQueue.size() != 0) { 424 | this.shouldResetTick = true; 425 | this.animationState = AnimationState.Transitioning; 426 | this.justStartedTransition = true; 427 | this.needsAnimationReload = false; 428 | adjustedTick = adjustTick(tick); 429 | } 430 | else if (this.animationState != AnimationState.Transitioning) { 431 | this.animationState = AnimationState.Running; 432 | } 433 | 434 | // Handle transitioning to a different animation (or just starting one) 435 | if (this.animationState == AnimationState.Transitioning) { 436 | // Just started transitioning, so set the current animation to the first one 437 | if (adjustedTick == 0 || this.isJustStarting) { 438 | this.justStartedTransition = false; 439 | this.currentAnimation = animationQueue.poll(); 440 | 441 | resetEventKeyFrames(); 442 | saveSnapshotsForAnimation(this.currentAnimation, boneSnapshotCollection); 443 | } 444 | if (this.currentAnimation != null) { 445 | setAnimTime(parser, 0); 446 | 447 | for (BoneAnimation boneAnimation : this.currentAnimation.boneAnimations) { 448 | BoneAnimationQueue boneAnimationQueue = this.boneAnimationQueues.get(boneAnimation.boneName); 449 | BoneSnapshot boneSnapshot = this.boneSnapshots.get(boneAnimation.boneName); 450 | Optional first = Optional.empty(); 451 | 452 | for (IBone bone : modelRendererList) { 453 | if (bone.getName().equals(boneAnimation.boneName)) { 454 | first = Optional.of(bone); 455 | break; 456 | } 457 | } 458 | 459 | if (first.isEmpty()) { 460 | if (crashWhenCantFindBone) 461 | throw new RuntimeException("Could not find bone: " + boneAnimation.boneName); 462 | 463 | continue; 464 | } 465 | 466 | BoneSnapshot initialSnapshot = first.get().getInitialSnapshot(); 467 | assert boneSnapshot != null : "Bone snapshot was null"; 468 | 469 | VectorKeyFrameList> rotationKeyFrames = boneAnimation.rotationKeyFrames; 470 | VectorKeyFrameList> positionKeyFrames = boneAnimation.positionKeyFrames; 471 | VectorKeyFrameList> scaleKeyFrames = boneAnimation.scaleKeyFrames; 472 | 473 | // Adding the initial positions of the upcoming animation, so the model 474 | // transitions to the initial state of the new animation 475 | if (!rotationKeyFrames.xKeyFrames.isEmpty()) { 476 | AnimationPoint xPoint = getAnimationPointAtTick(rotationKeyFrames.xKeyFrames, 0, true, Axis.X); 477 | AnimationPoint yPoint = getAnimationPointAtTick(rotationKeyFrames.yKeyFrames, 0, true, Axis.Y); 478 | AnimationPoint zPoint = getAnimationPointAtTick(rotationKeyFrames.zKeyFrames, 0, true, Axis.Z); 479 | boneAnimationQueue.rotationXQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 480 | boneSnapshot.rotationValueX - initialSnapshot.rotationValueX, 481 | xPoint.animationStartValue)); 482 | boneAnimationQueue.rotationYQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 483 | boneSnapshot.rotationValueY - initialSnapshot.rotationValueY, 484 | yPoint.animationStartValue)); 485 | boneAnimationQueue.rotationZQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 486 | boneSnapshot.rotationValueZ - initialSnapshot.rotationValueZ, 487 | zPoint.animationStartValue)); 488 | } 489 | 490 | if (!positionKeyFrames.xKeyFrames.isEmpty()) { 491 | AnimationPoint xPoint = getAnimationPointAtTick(positionKeyFrames.xKeyFrames, 0, false, Axis.X); 492 | AnimationPoint yPoint = getAnimationPointAtTick(positionKeyFrames.yKeyFrames, 0, false, Axis.Y); 493 | AnimationPoint zPoint = getAnimationPointAtTick(positionKeyFrames.zKeyFrames, 0, false, Axis.Z); 494 | boneAnimationQueue.positionXQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 495 | boneSnapshot.positionOffsetX, xPoint.animationStartValue)); 496 | boneAnimationQueue.positionYQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 497 | boneSnapshot.positionOffsetY, yPoint.animationStartValue)); 498 | boneAnimationQueue.positionZQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 499 | boneSnapshot.positionOffsetZ, zPoint.animationStartValue)); 500 | } 501 | 502 | if (!scaleKeyFrames.xKeyFrames.isEmpty()) { 503 | AnimationPoint xPoint = getAnimationPointAtTick(scaleKeyFrames.xKeyFrames, 0, false, Axis.X); 504 | AnimationPoint yPoint = getAnimationPointAtTick(scaleKeyFrames.yKeyFrames, 0, false, Axis.Y); 505 | AnimationPoint zPoint = getAnimationPointAtTick(scaleKeyFrames.zKeyFrames, 0, false, Axis.Z); 506 | boneAnimationQueue.scaleXQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 507 | boneSnapshot.scaleValueX, xPoint.animationStartValue)); 508 | boneAnimationQueue.scaleYQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 509 | boneSnapshot.scaleValueY, yPoint.animationStartValue)); 510 | boneAnimationQueue.scaleZQueue().add(new AnimationPoint(null, adjustedTick, this.transitionLengthTicks, 511 | boneSnapshot.scaleValueZ, zPoint.animationStartValue)); 512 | } 513 | } 514 | } 515 | } 516 | else if (getAnimationState() == AnimationState.Running) { 517 | // Actually run the animation 518 | processCurrentAnimation(adjustedTick, tick, parser, crashWhenCantFindBone); 519 | } 520 | } 521 | 522 | private void setAnimTime(MolangParser parser, final double tick) { 523 | parser.setValue("query.anim_time", () -> tick / 20); 524 | } 525 | 526 | private IAnimatableModel getModel(T animatable) { 527 | for (ModelFetcher modelFetcher : modelFetchers) { 528 | IAnimatableModel model = (IAnimatableModel) modelFetcher.apply(animatable); 529 | 530 | if (model != null) 531 | return model; 532 | } 533 | 534 | System.out.printf( 535 | "Could not find suitable model for animatable of type %s. Did you register a Model Fetcher?%n", 536 | animatable.getClass()); 537 | 538 | return null; 539 | } 540 | 541 | protected PlayState testAnimationPredicate(AnimationEvent event) { 542 | return this.animationPredicate.test(event); 543 | } 544 | 545 | // At the beginning of a new transition, save a snapshot of the model's 546 | // rotation, position, and scale values as the initial value to lerp from 547 | private void saveSnapshotsForAnimation(Animation animation, 548 | Map> boneSnapshotCollection) { 549 | for (Pair snapshot : boneSnapshotCollection.values()) { 550 | if (animation != null && animation.boneAnimations != null) { 551 | for (BoneAnimation boneAnimation : animation.boneAnimations) { 552 | if (boneAnimation.boneName.equals(snapshot.getLeft().getName())) { 553 | this.boneSnapshots.put(boneAnimation.boneName, new BoneSnapshot(snapshot.getRight())); 554 | 555 | break; 556 | } 557 | } 558 | } 559 | } 560 | } 561 | 562 | private void processCurrentAnimation(double tick, double actualTick, MolangParser parser, 563 | boolean crashWhenCantFindBone) { 564 | assert currentAnimation != null; 565 | // Animation has ended 566 | if (tick >= this.currentAnimation.animationLength) { 567 | resetEventKeyFrames(); 568 | // If the current animation is set to loop, keep it as the current animation and 569 | // just start over 570 | if (!this.currentAnimation.loop.isRepeatingAfterEnd()) { 571 | // Pull the next animation from the queue 572 | Animation peek = this.animationQueue.peek(); 573 | 574 | if (peek == null) { 575 | // No more animations left, stop the animation controller 576 | this.animationState = AnimationState.Stopped; 577 | 578 | return; 579 | } 580 | else { 581 | // Otherwise, set the state to transitioning and start transitioning to the next 582 | // animation next frame 583 | this.animationState = AnimationState.Transitioning; 584 | this.shouldResetTick = true; 585 | this.currentAnimation = this.animationQueue.peek(); 586 | } 587 | } 588 | else { 589 | // Reset the adjusted tick so the next animation starts at tick 0 590 | this.shouldResetTick = true; 591 | tick = adjustTick(actualTick); 592 | } 593 | } 594 | 595 | setAnimTime(parser, tick); 596 | 597 | // Loop through every boneanimation in the current animation and process the 598 | // values 599 | List boneAnimations = currentAnimation.boneAnimations; 600 | 601 | for (BoneAnimation boneAnimation : boneAnimations) { 602 | BoneAnimationQueue boneAnimationQueue = boneAnimationQueues.get(boneAnimation.boneName); 603 | 604 | if (boneAnimationQueue == null) { 605 | if (crashWhenCantFindBone) 606 | throw new RuntimeException("Could not find bone: " + boneAnimation.boneName); 607 | 608 | continue; 609 | } 610 | 611 | VectorKeyFrameList> rotationKeyFrames = boneAnimation.rotationKeyFrames; 612 | VectorKeyFrameList> positionKeyFrames = boneAnimation.positionKeyFrames; 613 | VectorKeyFrameList> scaleKeyFrames = boneAnimation.scaleKeyFrames; 614 | 615 | if (!rotationKeyFrames.xKeyFrames.isEmpty()) { 616 | boneAnimationQueue.rotationXQueue() 617 | .add(getAnimationPointAtTick(rotationKeyFrames.xKeyFrames, tick, true, Axis.X)); 618 | boneAnimationQueue.rotationYQueue() 619 | .add(getAnimationPointAtTick(rotationKeyFrames.yKeyFrames, tick, true, Axis.Y)); 620 | boneAnimationQueue.rotationZQueue() 621 | .add(getAnimationPointAtTick(rotationKeyFrames.zKeyFrames, tick, true, Axis.Z)); 622 | } 623 | 624 | if (!positionKeyFrames.xKeyFrames.isEmpty()) { 625 | boneAnimationQueue.positionXQueue() 626 | .add(getAnimationPointAtTick(positionKeyFrames.xKeyFrames, tick, false, Axis.X)); 627 | boneAnimationQueue.positionYQueue() 628 | .add(getAnimationPointAtTick(positionKeyFrames.yKeyFrames, tick, false, Axis.Y)); 629 | boneAnimationQueue.positionZQueue() 630 | .add(getAnimationPointAtTick(positionKeyFrames.zKeyFrames, tick, false, Axis.Z)); 631 | } 632 | 633 | if (!scaleKeyFrames.xKeyFrames.isEmpty()) { 634 | boneAnimationQueue.scaleXQueue() 635 | .add(getAnimationPointAtTick(scaleKeyFrames.xKeyFrames, tick, false, Axis.X)); 636 | boneAnimationQueue.scaleYQueue() 637 | .add(getAnimationPointAtTick(scaleKeyFrames.yKeyFrames, tick, false, Axis.Y)); 638 | boneAnimationQueue.scaleZQueue() 639 | .add(getAnimationPointAtTick(scaleKeyFrames.zKeyFrames, tick, false, Axis.Z)); 640 | } 641 | } 642 | 643 | if (this.soundListener != null || this.particleListener != null || this.customInstructionListener != null) { 644 | for (EventKeyFrame soundKeyFrame : this.currentAnimation.soundKeyFrames) { 645 | if (!this.executedKeyFrames.contains(soundKeyFrame) && tick >= soundKeyFrame.getStartTick()) { 646 | SoundKeyframeEvent event = new SoundKeyframeEvent<>(this.animatable, tick, 647 | soundKeyFrame.getEventData(), this); 648 | 649 | this.soundListener.playSound(event); 650 | this.executedKeyFrames.add(soundKeyFrame); 651 | } 652 | } 653 | 654 | for (ParticleEventKeyFrame particleEventKeyFrame : this.currentAnimation.particleKeyFrames) { 655 | if (!this.executedKeyFrames.contains(particleEventKeyFrame) 656 | && tick >= particleEventKeyFrame.getStartTick()) { 657 | ParticleKeyFrameEvent event = new ParticleKeyFrameEvent<>(this.animatable, tick, 658 | particleEventKeyFrame.effect, particleEventKeyFrame.locator, particleEventKeyFrame.script, 659 | this); 660 | 661 | this.particleListener.summonParticle(event); 662 | this.executedKeyFrames.add(particleEventKeyFrame); 663 | } 664 | } 665 | 666 | for (EventKeyFrame customInstructionKeyFrame : currentAnimation.customInstructionKeyframes) { 667 | if (!this.executedKeyFrames.contains(customInstructionKeyFrame) 668 | && tick >= customInstructionKeyFrame.getStartTick()) { 669 | CustomInstructionKeyframeEvent event = new CustomInstructionKeyframeEvent<>(this.animatable, 670 | tick, customInstructionKeyFrame.getEventData(), this); 671 | 672 | this.customInstructionListener.executeInstruction(event); 673 | this.executedKeyFrames.add(customInstructionKeyFrame); 674 | } 675 | } 676 | } 677 | 678 | if (this.transitionLengthTicks == 0 && shouldResetTick && this.animationState == AnimationState.Transitioning) 679 | this.currentAnimation = animationQueue.poll(); 680 | } 681 | 682 | // Helper method to populate all the initial animation point queues 683 | private void createInitialQueues(List modelRendererList) { 684 | this.boneAnimationQueues.clear(); 685 | 686 | for (IBone modelRenderer : modelRendererList) { 687 | this.boneAnimationQueues.put(modelRenderer.getName(), new BoneAnimationQueue(modelRenderer)); 688 | } 689 | } 690 | 691 | // Used to reset the "tick" everytime a new animation starts, a transition 692 | // starts, or something else of importance happens 693 | protected double adjustTick(double tick) { 694 | if (this.shouldResetTick) { 695 | if (getAnimationState() == AnimationState.Transitioning) { 696 | this.tickOffset = tick; 697 | } 698 | else if (getAnimationState() == AnimationState.Running) { 699 | this.tickOffset = tick; 700 | } 701 | 702 | this.shouldResetTick = false; 703 | 704 | return 0; 705 | } 706 | else { 707 | // assert tick - this.tickOffset >= 0; 708 | return this.animationSpeed * Math.max(tick - this.tickOffset, 0.0D); 709 | } 710 | } 711 | 712 | // Helper method to transform a KeyFrameLocation to an AnimationPoint 713 | private AnimationPoint getAnimationPointAtTick(List> frames, double tick, boolean isRotation, 714 | Axis axis) { 715 | KeyFrameLocation> location = getCurrentKeyFrameLocation(frames, tick); 716 | KeyFrame currentFrame = location.currentFrame; 717 | double startValue = currentFrame.getStartValue().get(); 718 | double endValue = currentFrame.getEndValue().get(); 719 | 720 | if (isRotation) { 721 | if (!(currentFrame.getStartValue() instanceof ConstantValue)) { 722 | startValue = Math.toRadians(startValue); 723 | 724 | if (axis == Axis.X || axis == Axis.Y) 725 | startValue *= -1; 726 | } 727 | 728 | if (!(currentFrame.getEndValue() instanceof ConstantValue)) { 729 | endValue = Math.toRadians(endValue); 730 | 731 | if (axis == Axis.X || axis == Axis.Y) 732 | endValue *= -1; 733 | } 734 | } 735 | 736 | return new AnimationPoint(currentFrame, location.currentTick, currentFrame.getLength(), startValue, endValue); 737 | } 738 | 739 | /** 740 | * Returns the current keyframe object, plus how long the previous keyframes 741 | * have taken (aka elapsed animation time) 742 | **/ 743 | private KeyFrameLocation> getCurrentKeyFrameLocation(List> frames, 744 | double ageInTicks) { 745 | double totalTimeTracker = 0; 746 | 747 | for (KeyFrame frame : frames) { 748 | totalTimeTracker += frame.getLength(); 749 | 750 | if (totalTimeTracker > ageInTicks) { 751 | double tick = (ageInTicks - (totalTimeTracker - frame.getLength())); 752 | 753 | return new KeyFrameLocation<>(frame, tick); 754 | } 755 | } 756 | 757 | return new KeyFrameLocation<>(frames.get(frames.size() - 1), ageInTicks); 758 | } 759 | 760 | private void resetEventKeyFrames() { 761 | this.executedKeyFrames.clear(); 762 | } 763 | 764 | public void markNeedsReload() { 765 | this.needsAnimationReload = true; 766 | } 767 | 768 | public void clearAnimationCache() { 769 | this.currentAnimationBuilder = new AnimationBuilder(); 770 | } 771 | 772 | public double getAnimationSpeed() { 773 | return this.animationSpeed; 774 | } 775 | 776 | public void setAnimationSpeed(double animationSpeed) { 777 | this.animationSpeed = animationSpeed; 778 | } 779 | 780 | @FunctionalInterface 781 | public interface ModelFetcher extends Function> {} 782 | } --------------------------------------------------------------------------------