251 lines
10 KiB
Java
251 lines
10 KiB
Java
package software.bernie.geckolib.model;
|
|
|
|
import org.jetbrains.annotations.ApiStatus;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import software.bernie.geckolib.GeckoLibConstants;
|
|
import software.bernie.geckolib.animatable.GeoAnimatable;
|
|
import software.bernie.geckolib.animatable.GeoReplacedEntity;
|
|
import software.bernie.geckolib.animation.AnimatableManager;
|
|
import software.bernie.geckolib.animation.Animation;
|
|
import software.bernie.geckolib.animation.AnimationProcessor;
|
|
import software.bernie.geckolib.animation.AnimationState;
|
|
import software.bernie.geckolib.cache.GeckoLibCache;
|
|
import software.bernie.geckolib.cache.object.BakedGeoModel;
|
|
import software.bernie.geckolib.cache.object.GeoBone;
|
|
import software.bernie.geckolib.constant.DataTickets;
|
|
import software.bernie.geckolib.constant.dataticket.DataTicket;
|
|
import software.bernie.geckolib.loading.object.BakedAnimations;
|
|
import software.bernie.geckolib.renderer.GeoRenderer;
|
|
import software.bernie.geckolib.util.RenderUtil;
|
|
|
|
import java.util.Optional;
|
|
import java.util.function.BiConsumer;
|
|
import java.util.function.DoubleSupplier;
|
|
import net.minecraft.class_1297;
|
|
import net.minecraft.class_1921;
|
|
import net.minecraft.class_2960;
|
|
import net.minecraft.class_310;
|
|
|
|
/**
|
|
* Base class for all code-based model objects
|
|
* <p>
|
|
* All models registered to a {@link GeoRenderer} must be an instance of this or one of its subclasses
|
|
*
|
|
* @see <a href="https://github.com/bernie-g/geckolib/wiki/Models">GeckoLib Wiki - Models</a>
|
|
*/
|
|
public abstract class GeoModel<T extends GeoAnimatable> {
|
|
private final AnimationProcessor<T> processor = new AnimationProcessor<>(this);
|
|
|
|
private BakedGeoModel currentModel = null;
|
|
private double animTime;
|
|
private double lastGameTickTime;
|
|
private long lastRenderedInstance = -1;
|
|
|
|
/**
|
|
* Returns the resource path for the {@link BakedGeoModel} (model json file) to render based on the provided animatable
|
|
*/
|
|
public class_2960 getModelResource(T animatable, @Nullable GeoRenderer<T> renderer) {
|
|
return getModelResource(animatable);
|
|
}
|
|
|
|
/**
|
|
* Returns the resource path for the {@link BakedGeoModel} (model json file) to render based on the provided animatable
|
|
*/
|
|
@Deprecated
|
|
public abstract class_2960 getModelResource(T animatable);
|
|
|
|
/**
|
|
* Returns the resource path for the texture file to render based on the provided animatable
|
|
*/
|
|
public class_2960 getTextureResource(T animatable, @Nullable GeoRenderer<T> renderer) {
|
|
return getTextureResource(animatable);
|
|
}
|
|
|
|
/**
|
|
* Returns the resource path for the texture file to render based on the provided animatable
|
|
*/
|
|
@Deprecated
|
|
public abstract class_2960 getTextureResource(T animatable);
|
|
|
|
/**
|
|
* Returns the resource path for the {@link BakedAnimations} (animation json file) to use for animations based on the provided animatable
|
|
*/
|
|
public abstract class_2960 getAnimationResource(T animatable);
|
|
|
|
/**
|
|
* Returns the resource path for the {@link BakedAnimations} (animation json file) fallback locations in the event
|
|
* your animation isn't present in the {@link #getAnimationResource(GeoAnimatable) primary resource}.
|
|
* <p>
|
|
* Should <b><u>NOT</u></b> be used as the primary animation resource path, and in general shouldn't be used
|
|
* at all unless you know what you are doing
|
|
*/
|
|
public class_2960[] getAnimationResourceFallbacks(T animatable) {
|
|
return new class_2960[0];
|
|
}
|
|
|
|
/**
|
|
* Override this and return true if Geckolib should crash when attempting to animate the model, but fails to find a bone
|
|
* <p>
|
|
* By default, GeckoLib will just gracefully ignore a missing bone, which might cause oddities with incorrect models or mismatching variables
|
|
*/
|
|
public boolean crashIfBoneMissing() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the default render type for this animatable, to be selected by default by the renderer using it
|
|
*
|
|
* @return Return the RenderType to use, or null to prevent the model rendering. Returning null will not prevent animation functions taking place
|
|
*/
|
|
@Nullable
|
|
public class_1921 getRenderType(T animatable, class_2960 texture) {
|
|
return class_1921.method_23578(texture);
|
|
}
|
|
|
|
/**
|
|
* Get the baked geo model object used for rendering from the given resource path
|
|
*/
|
|
public BakedGeoModel getBakedModel(class_2960 location) {
|
|
BakedGeoModel model = GeckoLibCache.getBakedModels().get(location);
|
|
|
|
if (model == null) {
|
|
if (!location.method_12832().contains("geo/"))
|
|
throw GeckoLibConstants.exception(location, "Invalid model resource path provided - GeckoLib models must be placed in assets/<modid>/geo/");
|
|
|
|
throw GeckoLibConstants.exception(location, "Unable to find model");
|
|
}
|
|
|
|
if (model != this.currentModel) {
|
|
this.processor.setActiveModel(model);
|
|
this.currentModel = model;
|
|
}
|
|
|
|
return this.currentModel;
|
|
}
|
|
|
|
/**
|
|
* Gets a bone from this model by name
|
|
*
|
|
* @param name The name of the bone
|
|
* @return An {@link Optional} containing the {@link software.bernie.geckolib.cache.object.GeoBone} if one matches, otherwise an empty Optional
|
|
*/
|
|
public Optional<GeoBone> getBone(String name) {
|
|
return Optional.ofNullable(getAnimationProcessor().getBone(name));
|
|
}
|
|
|
|
/**
|
|
* Gets the loaded {@link Animation} for the given animation {@code name}, if it exists
|
|
*
|
|
* @param animatable The {@code GeoAnimatable} instance being referred to
|
|
* @param name The name of the animation to retrieve
|
|
* @return The {@code Animation} instance for the provided {@code name}, or null if none match
|
|
*/
|
|
@Nullable
|
|
public Animation getAnimation(T animatable, String name) {
|
|
class_2960 location = getAnimationResource(animatable);
|
|
BakedAnimations bakedAnimations = GeckoLibCache.getBakedAnimations().get(location);
|
|
Animation animation = bakedAnimations != null ? bakedAnimations.getAnimation(name) : null;
|
|
|
|
if (animation != null)
|
|
return animation;
|
|
|
|
for (class_2960 fallbackLocation : getAnimationResourceFallbacks(animatable)) {
|
|
bakedAnimations = GeckoLibCache.getBakedAnimations().get(location = fallbackLocation);
|
|
animation = bakedAnimations != null ? bakedAnimations.getAnimation(name) : null;
|
|
|
|
if (animation != null)
|
|
return animation;
|
|
}
|
|
|
|
if (bakedAnimations == null) {
|
|
if (!location.method_12832().contains("animations/"))
|
|
throw GeckoLibConstants.exception(location, "Invalid animation resource path provided - GeckoLib animations must be placed in assets/<modid>/animations/");
|
|
|
|
throw GeckoLibConstants.exception(location, "Unable to find animation file.");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link AnimationProcessor} for this model.
|
|
*/
|
|
public AnimationProcessor<T> getAnimationProcessor() {
|
|
return this.processor;
|
|
}
|
|
|
|
/**
|
|
* Add additional {@link DataTicket DataTickets} to the {@link AnimationState} to be handled by your animation handler at render time
|
|
*
|
|
* @param animatable The animatable instance currently being animated
|
|
* @param instanceId The unique instance id of the animatable being animated
|
|
* @param dataConsumer The DataTicket + data consumer to be added to the AnimationEvent
|
|
*/
|
|
public void addAdditionalStateData(T animatable, long instanceId, BiConsumer<DataTicket<T>, T> dataConsumer) {}
|
|
|
|
/**
|
|
* This method is called once per render-frame for each {@link GeoAnimatable} being rendered
|
|
* <p>
|
|
* It is an internal method for automated animation parsing. Use {@link GeoModel#setCustomAnimations(GeoAnimatable, long, AnimationState)} for custom animation work
|
|
*/
|
|
@ApiStatus.Internal
|
|
public void handleAnimations(T animatable, long instanceId, AnimationState<T> animationState, float partialTick) {
|
|
class_310 mc = class_310.method_1551();
|
|
AnimatableManager<T> animatableManager = animatable.getAnimatableInstanceCache().getManagerForId(instanceId);
|
|
Double currentTick = animationState.getData(DataTickets.TICK);
|
|
|
|
if (currentTick == null)
|
|
currentTick = animatable instanceof class_1297 entity ? (double)entity.field_6012 : RenderUtil.getCurrentTick();
|
|
|
|
if (animatableManager.getFirstTickTime() == -1)
|
|
animatableManager.startedAt(currentTick + partialTick);
|
|
|
|
double currentFrameTime = animatable instanceof class_1297 || animatable instanceof GeoReplacedEntity ? currentTick + partialTick : currentTick - animatableManager.getFirstTickTime();
|
|
boolean isReRender = !animatableManager.isFirstTick() && currentFrameTime == animatableManager.getLastUpdateTime();
|
|
|
|
if (isReRender && instanceId == this.lastRenderedInstance)
|
|
return;
|
|
|
|
if (!mc.method_1493() || animatable.shouldPlayAnimsWhileGamePaused()) {
|
|
animatableManager.updatedAt(currentFrameTime);
|
|
|
|
double lastUpdateTime = animatableManager.getLastUpdateTime();
|
|
this.animTime += lastUpdateTime - this.lastGameTickTime;
|
|
this.lastGameTickTime = lastUpdateTime;
|
|
}
|
|
|
|
animationState.animationTick = this.animTime;
|
|
this.lastRenderedInstance = instanceId;
|
|
AnimationProcessor<T> processor = getAnimationProcessor();
|
|
|
|
processor.preAnimationSetup(animationState, this.animTime);
|
|
|
|
if (!processor.getRegisteredBones().isEmpty())
|
|
processor.tickAnimation(animatable, this, animatableManager, this.animTime, animationState, crashIfBoneMissing());
|
|
|
|
setCustomAnimations(animatable, instanceId, animationState);
|
|
}
|
|
|
|
/**
|
|
* This method is called once per render-frame for each {@link GeoAnimatable} being rendered
|
|
* <p>
|
|
* Override to set custom animations (such as head rotation, etc)
|
|
*
|
|
* @param animatable The {@code GeoAnimatable} instance currently being rendered
|
|
* @param instanceId The instance id of the {@code GeoAnimatable}
|
|
* @param animationState An {@link AnimationState} instance created to hold animation data for the {@code animatable} for this method call
|
|
*/
|
|
public void setCustomAnimations(T animatable, long instanceId, AnimationState<T> animationState) {}
|
|
|
|
/**
|
|
* This method is called once per render-frame for each {@link GeoAnimatable} being rendered
|
|
* <p>
|
|
* Use this method to set custom {@link software.bernie.geckolib.loading.math.value.Variable Variable} values via
|
|
* {@link software.bernie.geckolib.loading.math.MathParser#setVariable(String, DoubleSupplier) MathParser.setVariable}
|
|
*
|
|
* @param animationState The AnimationState data for the current render frame
|
|
* @param animTime The internal tick counter kept by the {@link AnimatableManager manager} for this animatable
|
|
*/
|
|
public void applyMolangQueries(AnimationState<T> animationState, double animTime) {}
|
|
}
|