package electroblob.wizardry.client.animation; import net.minecraft.client.model.ModelBase; import net.minecraft.client.model.ModelBiped; import net.minecraft.client.model.ModelPlayer; import net.minecraft.client.model.ModelRenderer; /** * A wrapper around {@link ModelRenderer} which can be swapped into models to allow render pre/post events to * circumvent {@code ModelBase.setRotationAngles(...)} and set model part rotations themselves. Instances of this class * keep a reference to the original {@code ModelRenderer} and delegate all the actual rendering to it. *

* {@link PlayerAnimator PlayerAnimator} does all the necessary setup with this * class for player animations. It is also possible to set up custom animations for non-player entities with this class, * see {@link ModelRendererExtended#wrap(ModelBiped)} and {@link ModelRendererExtended#wrap(ModelBase, ModelRenderer)}. * @author Electroblob * @since Wizardry 4.3 */ public class ModelRendererExtended extends ModelRenderer { // We probably could have copied all the fields over instead, but this way if anyone else modifies the boxes we // won't overwrite their changes // This way also avoids reflection because we don't need to copy private fields, which is better for performance private ModelRenderer delegate; private float actualRotationX; private float actualRotationY; private float actualRotationZ; private float extraRotationX; private float extraRotationY; private float extraRotationZ; private ModelRendererExtended(ModelBase baseModel, ModelRenderer delegate){ super(baseModel, delegate.boxName); this.delegate = delegate; // Copy over any public fields that might get changed this.textureWidth = delegate.textureWidth; this.textureHeight = delegate.textureHeight; this.showModel = delegate.showModel; this.isHidden = delegate.isHidden; this.rotationPointX = delegate.rotationPointX; this.rotationPointY = delegate.rotationPointY; this.rotationPointZ = delegate.rotationPointZ; this.cubeList = delegate.cubeList; this.childModels = delegate.childModels; this.resetRotation(); } // Static initialisers /** * Replaces the given part of the given {@link ModelBase} with a new {@code ModelRendererExtended} that wraps the * original part. This method takes care of modifying the model's box list, but is unable to change any * individual {@link ModelRenderer} fields - this method instead returns the resulting * {@code ModelRendererExtended} to be assigned to the appropriate field. * @param model The model whose part is to be wrapped * @param box The part of the model to wrap, must belong to the above model * @return The resulting wrapped model part, this should be assigned to the appropriate part field in the model. If * the given model part was already wrapped, this method simply returns it unchanged. * @throws IllegalArgumentException if the given box does not belong to the given model */ public static ModelRendererExtended wrap(ModelBase model, ModelRenderer box){ if(box instanceof ModelRendererExtended) return (ModelRendererExtended)box; // Ignore already-wrapped parts ModelRendererExtended wrapper = new ModelRendererExtended(model, box); int index = model.boxList.indexOf(box); if(index < 0) throw new IllegalArgumentException(String.format("The given ModelRenderer %s does not belong to the given model %s", box, model)); model.boxList.set(index, wrapper); // I doubt the order matters but we may as well put it at the same index return wrapper; } /** * Replaces the given {@link ModelBiped}'s parts with new {@code ModelRendererExtended} versions that wrap the * original parts. If the given model is a {@link ModelPlayer}, the extra boxes for player skin overlays will also * be wrapped. */ public static void wrap(ModelBiped model){ // MMmmmmmm wraps model.bipedHead = wrap(model, model.bipedHead); model.bipedBody = wrap(model, model.bipedBody); model.bipedRightArm = wrap(model, model.bipedRightArm); model.bipedLeftArm = wrap(model, model.bipedLeftArm); model.bipedRightLeg = wrap(model, model.bipedRightLeg); model.bipedLeftLeg = wrap(model, model.bipedLeftLeg); model.bipedHeadwear = wrap(model, model.bipedHeadwear); if(model instanceof ModelPlayer){ ((ModelPlayer)model).bipedBodyWear = wrap(model, ((ModelPlayer)model).bipedBodyWear); ((ModelPlayer)model).bipedRightArmwear = wrap(model, ((ModelPlayer)model).bipedRightArmwear); ((ModelPlayer)model).bipedLeftArmwear = wrap(model, ((ModelPlayer)model).bipedLeftArmwear); ((ModelPlayer)model).bipedRightLegwear = wrap(model, ((ModelPlayer)model).bipedRightLegwear); ((ModelPlayer)model).bipedLeftLegwear = wrap(model, ((ModelPlayer)model).bipedLeftLegwear); } } /** * Returns whether the given {@link ModelBiped} is fully wrapped, i.e. all its parts are instances of * {@code ModelRendererExtended}. If the given model is a {@link ModelPlayer}, this also checks the extra boxes for * player skin overlays. */ public static boolean isWrapped(ModelBiped model){ boolean flag = true; if(model instanceof ModelPlayer){ flag = flag && ((ModelPlayer)model).bipedBodyWear instanceof ModelRendererExtended; flag = flag && ((ModelPlayer)model).bipedRightArmwear instanceof ModelRendererExtended; flag = flag && ((ModelPlayer)model).bipedLeftArmwear instanceof ModelRendererExtended; flag = flag && ((ModelPlayer)model).bipedRightLegwear instanceof ModelRendererExtended; flag = flag && ((ModelPlayer)model).bipedLeftLegwear instanceof ModelRendererExtended; } return flag && model.bipedHead instanceof ModelRendererExtended && model.bipedBody instanceof ModelRendererExtended && model.bipedRightArm instanceof ModelRendererExtended && model.bipedLeftArm instanceof ModelRendererExtended && model.bipedRightLeg instanceof ModelRendererExtended && model.bipedLeftLeg instanceof ModelRendererExtended && model.bipedHeadwear instanceof ModelRendererExtended; } /** Resets the rotation of this model part to the angle set by the parent model. */ public void resetRotation(){ this.actualRotationX = Float.NaN; this.actualRotationY = Float.NaN; this.actualRotationZ = Float.NaN; this.extraRotationX = 0; this.extraRotationY = 0; this.extraRotationZ = 0; } /** Sets the rotation of this model part, which will overwrite the angle set by the parent model. */ public void setRotation(float x, float y, float z){ this.actualRotationX = x; this.actualRotationY = y; this.actualRotationZ = z; } /** Sets the extra rotation of this model part, which will be added onto the angle set by the parent model. */ public void addRotation(float x, float y, float z){ this.extraRotationX = x; this.extraRotationY = y; this.extraRotationZ = z; } /** Sets the rotation of this model part to the same values as the given box. */ public void setRotationTo(ModelRendererExtended box){ this.actualRotationX = box.actualRotationX; this.actualRotationY = box.actualRotationY; this.actualRotationZ = box.actualRotationZ; this.extraRotationX = box.extraRotationX; this.extraRotationY = box.extraRotationY; this.extraRotationZ = box.extraRotationZ; } // Delegate rendering, but fiddle with the angles first @Override public void render(float scale){ // Need to copy these over each time in case they were changed delegate.showModel = this.showModel; delegate.isHidden = this.isHidden; delegate.rotationPointX = this.rotationPointX; delegate.rotationPointY = this.rotationPointY; delegate.rotationPointZ = this.rotationPointZ; if(!Float.isNaN(actualRotationX) && !Float.isNaN(actualRotationY) && !Float.isNaN(actualRotationZ)){ delegate.rotateAngleX = actualRotationX; delegate.rotateAngleY = actualRotationY; delegate.rotateAngleZ = actualRotationZ; }else{ delegate.rotateAngleX = this.rotateAngleX + extraRotationX; delegate.rotateAngleY = this.rotateAngleY + extraRotationY; delegate.rotateAngleZ = this.rotateAngleZ + extraRotationZ; } delegate.render(scale); } // Delegate all other methods @Override public void addChild(ModelRenderer renderer){ delegate.addChild(renderer); } @Override public void renderWithRotation(float scale){ delegate.renderWithRotation(scale); } @Override public void postRender(float scale){ // Exactly the same setup as above, just add item rotation/translation fields and setters // float angle = 1; // float radius = 10; // delegate.rotationPointY -= radius * MathHelper.cos(angle); // delegate.rotationPointZ -= radius * MathHelper.sin(angle); // delegate.rotateAngleX += angle; delegate.postRender(scale); } }