From 34b1a49ded289b7523ced2038d878967136bf11a Mon Sep 17 00:00:00 2001 From: James Pelter Date: Fri, 28 Feb 2025 20:48:30 -0600 Subject: [PATCH] Fixed joint transform interpolation, working on improved quaternion interpolation. --- .../FirstPersonPlayerJointAnimator.java | 26 ++++++--- .../data/AnimationDataContainer.java | 23 ++++---- .../animation/data/driver/Driver.java | 6 +- .../animation/joint/JointTransform.java | 55 ++++++++++++++---- .../pose/function/BlendMultipleFunction.java | 2 +- .../function/JointTransformerFunction.java | 56 +++++++++++++++---- .../animation/pose/function/PoseFunction.java | 7 +-- 7 files changed, 123 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/trainguy9512/animationoverhaul/animation/animator/entity/FirstPersonPlayerJointAnimator.java b/src/main/java/com/trainguy9512/animationoverhaul/animation/animator/entity/FirstPersonPlayerJointAnimator.java index e431e9c..40dcda5 100644 --- a/src/main/java/com/trainguy9512/animationoverhaul/animation/animator/entity/FirstPersonPlayerJointAnimator.java +++ b/src/main/java/com/trainguy9512/animationoverhaul/animation/animator/entity/FirstPersonPlayerJointAnimator.java @@ -1,11 +1,12 @@ package com.trainguy9512.animationoverhaul.animation.animator.entity; +import com.mojang.math.Axis; +import com.trainguy9512.animationoverhaul.AnimationOverhaulMain; import com.trainguy9512.animationoverhaul.animation.data.*; import com.trainguy9512.animationoverhaul.animation.data.driver.Driver; import com.trainguy9512.animationoverhaul.animation.data.key.AnimationDriverKey; import com.trainguy9512.animationoverhaul.animation.pose.AnimationPose; import com.trainguy9512.animationoverhaul.animation.joint.JointTransform; -import com.trainguy9512.animationoverhaul.animation.pose.ComponentSpacePose; import com.trainguy9512.animationoverhaul.animation.pose.LocalSpacePose; import com.trainguy9512.animationoverhaul.animation.pose.function.*; import com.trainguy9512.animationoverhaul.animation.pose.function.cache.SavedCachedPoseContainer; @@ -112,23 +113,30 @@ public PoseFunction constructPoseFunction(SavedCachedPoseContain Random random = new Random(); PoseFunction testSequencePlayer = SequencePlayerFunction.builder(ANIMATION_FP_PLAYER_IDLE) .setLooping(true) - .setPlayRate((context) -> random.nextFloat(3)) + .setPlayRate((context) -> 1f) .build(); //cachedPoseContainer.register("TEST_SEQ_PLAYER", testSequencePlayer); PoseFunction testTransformer = LocalPoseConversionFunction.of( JointTransformerFunction.componentSpaceBuilder(ComponentPoseConversionFunction.of(testSequencePlayer), LEFT_ARM_JOINT) - .setTranslationConfiguration(JointTransformerFunction.TransformChannelConfiguration.of( - (context) -> new Vector3f((float) Math.sin(context.gameTime() * 8f) * 0.6f, 0, 0), - JointTransform.TransformType.ADD, - JointTransform.TransformSpace.COMPONENT)) - .build()); + .setTranslation( + (context) -> new Vector3f((float) Math.sin(context.gameTimeSeconds() * 8f) * 0f, 0, 0), + JointTransform.TransformType.ADD, + JointTransform.TransformSpace.COMPONENT + ) + .setRotation( + (context) -> Axis.XP.rotation((float) (Math.sin(context.gameTimeSeconds() * 0f) * Mth.PI * 0.3f)), + JointTransform.TransformType.REPLACE, + JointTransform.TransformSpace.COMPONENT + ) + .setWeight(context -> Mth.sin(context.gameTimeSeconds() * 12f) * 0.5f + 0.5f) + .build()); //PoseFunction cached = cachedPoseContainer.getOrThrow("TEST_SEQ_PLAYER"); PoseFunction blendMultipleFunction = BlendMultipleFunction.builder(testSequencePlayer).addBlendInput(testSequencePlayer, (evaluationState) -> 0.5f).build(); - return blendMultipleFunction; + return testTransformer; } @@ -171,8 +179,10 @@ public void extractAnimationData(LocalPlayer dataReference, OnTickDataContainer driverContainer.loadValueIntoDriver(CAMERA_ROTATION, targetRotation); + Vector3f dampenedCameraRotation = new Vector3f(driverContainer.getPreviousDriverValue(DAMPENED_CAMERA_ROTATION)); + // If the dampened camera rotation is 0 (which is what it is upon initialization), set it to the target if(dampenedCameraRotation.x() == 0F && dampenedCameraRotation.y() == 0F){ dampenedCameraRotation = targetRotation; diff --git a/src/main/java/com/trainguy9512/animationoverhaul/animation/data/AnimationDataContainer.java b/src/main/java/com/trainguy9512/animationoverhaul/animation/data/AnimationDataContainer.java index 60d4250..bd2bae6 100644 --- a/src/main/java/com/trainguy9512/animationoverhaul/animation/data/AnimationDataContainer.java +++ b/src/main/java/com/trainguy9512/animationoverhaul/animation/data/AnimationDataContainer.java @@ -1,23 +1,16 @@ package com.trainguy9512.animationoverhaul.animation.data; import com.google.common.collect.Maps; -import com.trainguy9512.animationoverhaul.AnimationOverhaulMain; import com.trainguy9512.animationoverhaul.animation.animator.JointAnimator; -import com.trainguy9512.animationoverhaul.animation.animator.entity.FirstPersonPlayerJointAnimator; import com.trainguy9512.animationoverhaul.animation.data.driver.Driver; import com.trainguy9512.animationoverhaul.animation.data.key.AnimationDataKey; import com.trainguy9512.animationoverhaul.animation.data.key.AnimationDriverKey; import com.trainguy9512.animationoverhaul.animation.joint.JointSkeleton; -import com.trainguy9512.animationoverhaul.animation.pose.AnimationPose; import com.trainguy9512.animationoverhaul.animation.pose.LocalSpacePose; import com.trainguy9512.animationoverhaul.animation.pose.function.PoseFunction; import com.trainguy9512.animationoverhaul.animation.pose.function.cache.SavedCachedPoseContainer; -import com.trainguy9512.animationoverhaul.animation.pose.sampler.PoseSampler; -import com.trainguy9512.animationoverhaul.animation.pose.sampler.Sampleable; -import com.trainguy9512.animationoverhaul.animation.pose.sampler.SampleableFromInput; import com.trainguy9512.animationoverhaul.util.Interpolator; -import java.util.HashMap; import java.util.Map; public class AnimationDataContainer implements PoseCalculationDataContainer, OnTickDataContainer { @@ -25,19 +18,19 @@ public class AnimationDataContainer implements PoseCalculationDataContainer, OnT private final Map>, Driver> drivers; private final SavedCachedPoseContainer savedCachedPoseContainer; private final PoseFunction poseFunction; - private long currentTick; private final JointSkeleton jointSkeleton; private final AnimationDriverKey perTickCalculatedPoseDriverKey; + private final AnimationDriverKey gameTimeTicksDriverKey; private AnimationDataContainer(JointAnimator jointAnimator){ this.drivers = Maps.newHashMap(); this.savedCachedPoseContainer = SavedCachedPoseContainer.of(); this.poseFunction = jointAnimator.constructPoseFunction(savedCachedPoseContainer).wrapUnique(); - this.currentTick = 0; this.jointSkeleton = jointAnimator.buildSkeleton(); this.perTickCalculatedPoseDriverKey = AnimationDriverKey.driverKeyOf("per_tick_calculated_pose", () -> Driver.ofInterpolatable(() -> LocalSpacePose.of(jointSkeleton), Interpolator.LOCAL_SPACE_POSE)); + this.gameTimeTicksDriverKey = AnimationDriverKey.driverKeyOf("game_time", () -> Driver.ofConstant(() -> 0L)); } public static AnimationDataContainer of(JointAnimator jointAnimator){ @@ -54,13 +47,17 @@ public AnimationDriverKey getPerTickCalculatedPoseDriverKey(){ } public void tick(){ - this.poseFunction.tick(PoseFunction.FunctionEvaluationState.of(this, false, this.currentTick)); - this.currentTick++; + this.loadValueIntoDriver(gameTimeTicksDriverKey, this.getDriverValue(gameTimeTicksDriverKey) + 1); + this.poseFunction.tick(PoseFunction.FunctionEvaluationState.of(this, false, this.getDriverValue(gameTimeTicksDriverKey))); } public LocalSpacePose computePose(float partialTicks){ this.savedCachedPoseContainer.clearCaches(); - return this.poseFunction.compute(PoseFunction.FunctionInterpolationContext.of(this, partialTicks)); + return this.poseFunction.compute(PoseFunction.FunctionInterpolationContext.of( + this, + partialTicks, + (this.getDriverValueInterpolated(gameTimeTicksDriverKey, 1) - (1 - partialTicks)) / 20f + )); } @SuppressWarnings("unchecked") @@ -94,6 +91,6 @@ public D getDriverValueInterpolated(AnimationDriverKey driverKey, float p } public void pushDriverValuesToPrevious(){ - this.drivers.values().forEach(Driver::pushValueToPrevious); + this.drivers.values().forEach(Driver::pushToPrevious); } } diff --git a/src/main/java/com/trainguy9512/animationoverhaul/animation/data/driver/Driver.java b/src/main/java/com/trainguy9512/animationoverhaul/animation/data/driver/Driver.java index c200b5f..fca8410 100644 --- a/src/main/java/com/trainguy9512/animationoverhaul/animation/data/driver/Driver.java +++ b/src/main/java/com/trainguy9512/animationoverhaul/animation/data/driver/Driver.java @@ -88,15 +88,15 @@ public void loadValue(D newValue){ * If the new value is null, the default value is used instead. * @param newValue Value to load for the current tick */ - public void loadValueAndPush(D newValue){ - this.pushValueToPrevious(); + public void pushToPreviousAndLoadValue(D newValue){ + this.pushToPrevious(); this.loadValue(newValue); } /** * Pushes the current value to the previous tick's value. */ - public void pushValueToPrevious(){ + public void pushToPrevious(){ this.valuePrevious = this.valueCurrent; } diff --git a/src/main/java/com/trainguy9512/animationoverhaul/animation/joint/JointTransform.java b/src/main/java/com/trainguy9512/animationoverhaul/animation/joint/JointTransform.java index 77a2263..c6c3490 100644 --- a/src/main/java/com/trainguy9512/animationoverhaul/animation/joint/JointTransform.java +++ b/src/main/java/com/trainguy9512/animationoverhaul/animation/joint/JointTransform.java @@ -6,6 +6,7 @@ import net.minecraft.client.model.geom.PartPose; import net.minecraft.resources.ResourceLocation; import org.joml.*; +import org.joml.Math; public final class JointTransform { @@ -54,6 +55,10 @@ public Quaternionf getRotation(){ return this.transform.getNormalizedRotation(new Quaternionf()); } + public Vector3f getScale(){ + return this.transform.getScale(new Vector3f()); + } + public Vector3f getEulerRotationZYX(){ return this.transform.getEulerAnglesZYX(new Vector3f()); } @@ -61,14 +66,21 @@ public Vector3f getEulerRotationZYX(){ public PartPose asPartPose(){ Vector3f rotation = this.getEulerRotationZYX(); Vector3f translation = this.getTranslation(); - return PartPose.offsetAndRotation( - translation.x(), - translation.y(), - translation.z(), - rotation.x(), - rotation.y(), - rotation.z() - ); + Vector3f scale = this.getScale(); + return PartPose + .offsetAndRotation( + translation.x(), + translation.y(), + translation.z(), + rotation.x(), + rotation.y(), + rotation.z() + ) + .scaled( + scale.x(), + scale.y(), + scale.z() + ); } public void translate(Vector3f translation, TransformSpace transformSpace, TransformType transformType){ @@ -89,11 +101,16 @@ public void rotate(Quaternionf rotation, TransformSpace transformSpace, Transfor switch (transformType){ case ADD -> { switch (transformSpace){ - case COMPONENT, PARENT -> this.transform.rotation(this.transform.getNormalizedRotation(new Quaternionf()).premul(rotation)); + //case COMPONENT, PARENT -> this.transform.rotation(this.transform.getNormalizedRotation(new Quaternionf()).premul(rotation)); case LOCAL -> this.transform.rotate(rotation); + case COMPONENT, PARENT -> { + Quaternionf currentRotation = this.transform.getUnnormalizedRotation(new Quaternionf()); + rotation.mul(currentRotation, currentRotation); + this.transform.translationRotateScale(this.getTranslation(), currentRotation, this.getScale()); + } } } - case REPLACE -> this.transform.rotation(rotation); + case REPLACE -> this.transform.translationRotateScale(this.getTranslation(), rotation, this.getScale()); } } @@ -126,7 +143,23 @@ public JointTransform mirrored(){ } public JointTransform interpolated(JointTransform other, float weight){ - return JointTransform.of(this.transform.lerp(other.transform, weight, new Matrix4f())); + Vector3f translation = this.transform.getTranslation(new Vector3f()); + Quaternionf rotation = this.transform.getNormalizedRotation(new Quaternionf()); + Vector3f scale = this.transform.getScale(new Vector3f()); + + Vector3f otherTranslation = other.transform.getTranslation(new Vector3f()); + Quaternionf otherRotation = other.transform.getNormalizedRotation(new Quaternionf()); + Vector3f otherScale = other.transform.getScale(new Vector3f()); + + if(rotation.dot(otherRotation) < 0){ + otherRotation.invert(otherRotation); + } + + translation.lerp(otherTranslation, weight); + rotation.slerp(otherRotation, weight); + scale.lerp(otherScale, weight); + + return JointTransform.of(new Matrix4f().translationRotateScale(translation, rotation, scale)); } public void translateAndRotatePoseStack(PoseStack poseStack){ diff --git a/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/BlendMultipleFunction.java b/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/BlendMultipleFunction.java index bc3e43e..b65601e 100644 --- a/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/BlendMultipleFunction.java +++ b/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/BlendMultipleFunction.java @@ -39,7 +39,7 @@ public BlendMultipleFunction(PoseFunction baseFunction, Map { - weightDriver.pushValueToPrevious(); + weightDriver.pushToPrevious(); float weight = blendInput.weightFunction.apply(evaluationState); weightDriver.loadValue(weight); diff --git a/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/JointTransformerFunction.java b/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/JointTransformerFunction.java index 935764b..127ae79 100644 --- a/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/JointTransformerFunction.java +++ b/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/JointTransformerFunction.java @@ -1,5 +1,6 @@ package com.trainguy9512.animationoverhaul.animation.pose.function; +import com.trainguy9512.animationoverhaul.animation.data.driver.Driver; import com.trainguy9512.animationoverhaul.animation.joint.JointTransform; import com.trainguy9512.animationoverhaul.animation.pose.AnimationPose; import com.trainguy9512.animationoverhaul.animation.pose.ComponentSpacePose; @@ -10,10 +11,26 @@ import java.util.function.Function; -public record JointTransformerFunction

(PoseFunction

input, String joint, TransformChannelConfiguration translationConfiguration, TransformChannelConfiguration rotationConfiguration, TransformChannelConfiguration scaleConfiguration) implements PoseFunction

{ +public class JointTransformerFunction

implements PoseFunction

{ + + private final PoseFunction

input; + private final String joint; + private final TransformChannelConfiguration translationConfiguration; + private final TransformChannelConfiguration rotationConfiguration; + private final TransformChannelConfiguration scaleConfiguration; + private final Function weightFunction; + + private JointTransformerFunction(PoseFunction

input, String joint, TransformChannelConfiguration translationConfiguration, TransformChannelConfiguration rotationConfiguration, TransformChannelConfiguration scaleConfiguration, Function weightFunction) { + this.input = input; + this.joint = joint; + this.translationConfiguration = translationConfiguration; + this.rotationConfiguration = rotationConfiguration; + this.scaleConfiguration = scaleConfiguration; + this.weightFunction = weightFunction; + } private static

JointTransformerFunction

of(Builder

builder){ - return new JointTransformerFunction<>(builder.input, builder.joint, builder.translationConfiguration, builder.rotationConfiguration, builder.scaleConfiguration); + return new JointTransformerFunction<>(builder.input, builder.joint, builder.translationConfiguration, builder.rotationConfiguration, builder.scaleConfiguration, builder.weightFunction); } @Override @@ -21,13 +38,21 @@ private static

JointTransformerFunction

of(Builder< if(!context.dataContainer().getJointSkeleton().containsJoint(this.joint)){ throw new IllegalArgumentException("Cannot run joint transformer function on joint " + this.joint + ", for it is not present within the skeleton."); } + P pose = this.input.compute(context); + float weight = this.weightFunction.apply(context); JointTransform jointTransform = pose.getJointTransform(this.joint); this.transformJoint(jointTransform, context, this.translationConfiguration, JointTransform::translate); this.transformJoint(jointTransform, context, this.rotationConfiguration, JointTransform::rotate); - pose.setJointTransform(this.joint, jointTransform); + if(weight != 0){ + if(weight == 1){ + pose.setJointTransform(this.joint, jointTransform); + } else { + pose.setJointTransform(this.joint, pose.getJointTransform(this.joint).interpolated(jointTransform, weight)); + } + } return pose; } @@ -42,7 +67,7 @@ public void tick(FunctionEvaluationState evaluationState) { @Override public PoseFunction

wrapUnique() { - return new JointTransformerFunction<>(this.input.wrapUnique(), this.joint, this.translationConfiguration, this.rotationConfiguration, this.scaleConfiguration); + return new JointTransformerFunction<>(this.input.wrapUnique(), this.joint, this.translationConfiguration, this.rotationConfiguration, this.scaleConfiguration, this.weightFunction); } public static Builder localOrParentSpaceBuilder(PoseFunction poseFunction, String joint){ @@ -60,6 +85,7 @@ public static class Builder

{ private TransformChannelConfiguration translationConfiguration; private TransformChannelConfiguration rotationConfiguration; private TransformChannelConfiguration scaleConfiguration; + private Function weightFunction; private Builder(PoseFunction

poseFunction, String joint){ this.joint = joint; @@ -67,20 +93,26 @@ private Builder(PoseFunction

poseFunction, String joint){ this.translationConfiguration = TransformChannelConfiguration.of((context) -> new Vector3f(0), JointTransform.TransformType.IGNORE, JointTransform.TransformSpace.LOCAL); this.rotationConfiguration = TransformChannelConfiguration.of((context) -> new Quaternionf().identity(), JointTransform.TransformType.IGNORE, JointTransform.TransformSpace.LOCAL); this.scaleConfiguration = TransformChannelConfiguration.of((context) -> new Vector3f(0), JointTransform.TransformType.IGNORE, JointTransform.TransformSpace.LOCAL); + this.weightFunction = evaluationState -> 1f; + } + + public Builder

setTranslation(Function transformFunction, JointTransform.TransformType transformType, JointTransform.TransformSpace transformSpace){ + this.translationConfiguration = TransformChannelConfiguration.of(transformFunction, transformType, transformSpace); + return this; } - public Builder

setTranslationConfiguration(TransformChannelConfiguration translationConfiguration){ - this.translationConfiguration = translationConfiguration; + public Builder

setRotation(Function transformFunction, JointTransform.TransformType transformType, JointTransform.TransformSpace transformSpace){ + this.rotationConfiguration = TransformChannelConfiguration.of(transformFunction, transformType, transformSpace); return this; } - public Builder

setRotationConfiguration(TransformChannelConfiguration rotationConfiguration){ - this.rotationConfiguration = rotationConfiguration; + public Builder

setScale(Function transformFunction, JointTransform.TransformType transformType, JointTransform.TransformSpace transformSpace){ + this.scaleConfiguration = TransformChannelConfiguration.of(transformFunction, transformType, transformSpace); return this; } - public Builder

setScaleConfiguration(TransformChannelConfiguration scaleConfiguration){ - this.scaleConfiguration = scaleConfiguration; + public Builder

setWeight(Function weightFunction){ + this.weightFunction = weightFunction; return this; } @@ -89,9 +121,9 @@ public JointTransformerFunction

build(){ } } - public record TransformChannelConfiguration(Function transformFunction, JointTransform.TransformType transformType, JointTransform.TransformSpace transformSpace){ + private record TransformChannelConfiguration(Function transformFunction, JointTransform.TransformType transformType, JointTransform.TransformSpace transformSpace){ - public static TransformChannelConfiguration of(Function transformFunction, JointTransform.TransformType transformType, JointTransform.TransformSpace transformSpace){ + private static TransformChannelConfiguration of(Function transformFunction, JointTransform.TransformType transformType, JointTransform.TransformSpace transformSpace){ return new TransformChannelConfiguration<>(transformFunction, transformType, transformSpace); } } diff --git a/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/PoseFunction.java b/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/PoseFunction.java index 9808ebd..3f3c6b0 100644 --- a/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/PoseFunction.java +++ b/src/main/java/com/trainguy9512/animationoverhaul/animation/pose/function/PoseFunction.java @@ -1,6 +1,5 @@ package com.trainguy9512.animationoverhaul.animation.pose.function; -import com.mojang.blaze3d.Blaze3D; import com.trainguy9512.animationoverhaul.animation.data.OnTickDataContainer; import com.trainguy9512.animationoverhaul.animation.data.PoseCalculationDataContainer; import com.trainguy9512.animationoverhaul.animation.pose.AnimationPose; @@ -47,9 +46,9 @@ public FunctionEvaluationState cancelMarkedForReset(){ } } - record FunctionInterpolationContext(PoseCalculationDataContainer dataContainer, float partialTicks, double gameTime) { - public static FunctionInterpolationContext of(PoseCalculationDataContainer dataContainer, float partialTicks){ - return new FunctionInterpolationContext(dataContainer, partialTicks, Blaze3D.getTime()); + record FunctionInterpolationContext(PoseCalculationDataContainer dataContainer, float partialTicks, float gameTimeSeconds) { + public static FunctionInterpolationContext of(PoseCalculationDataContainer dataContainer, float partialTicks, float gameTimeSeconds){ + return new FunctionInterpolationContext(dataContainer, partialTicks, gameTimeSeconds); } } }