Fix le bugs

This commit is contained in:
Sollace 2024-12-12 00:06:49 +01:00
parent a02fad8732
commit 00e69317e9
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
61 changed files with 234 additions and 181 deletions

View file

@ -1,20 +0,0 @@
package com.minelittlepony.api.model;
import net.minecraft.client.render.entity.model.BipedEntityModel.ArmPose;
import net.minecraft.client.render.entity.state.*;
import net.minecraft.util.Arm;
public interface HornedPonyModel<T extends EntityRenderState & PonyModel.AttributedHolder> extends PonyModel<T> {
/**
* Returns true if this model is currently using magic (horn is lit).
*/
default boolean isCasting(T state) {
return state instanceof PlayerEntityRenderState s
&& (getArmPoseForSide(s, Arm.LEFT) != ArmPose.EMPTY || getArmPoseForSide(s, Arm.RIGHT) != ArmPose.EMPTY);
}
@Override
default float getWobbleAmplitude(T state) {
return isCasting(state) ? 0 : 1;
}
}

View file

@ -2,7 +2,7 @@ package com.minelittlepony.api.model;
import com.minelittlepony.api.config.PonyConfig;
import com.minelittlepony.api.pony.*;
import com.minelittlepony.api.pony.meta.Wearable;
import com.minelittlepony.api.pony.meta.*;
import com.minelittlepony.common.util.animation.Interpolator;
import com.minelittlepony.util.MathUtil;
@ -126,6 +126,8 @@ public class ModelAttributes {
*/
public PonyData metadata = PonyData.NULL;
public Size size = SizePreset.NORMAL;
public Arm mainArm;
public Hand activeHand;
public ItemStack heldStack = ItemStack.EMPTY;
@ -160,6 +162,8 @@ public class ModelAttributes {
}
public void updateLivingState(LivingEntity entity, Pony pony, Mode mode) {
metadata = pony.metadata();
size = entity.isBaby() ? SizePreset.FOAL : PonyConfig.getEffectiveSize(metadata.size());
isPlayer = entity instanceof PlayerEntity;
visualHeight = entity.getHeight() + 0.125F;
isSitting = PonyPosture.isSitting(entity);

View file

@ -43,8 +43,10 @@ abstract class MixinHeldItemRenderer {
VertexConsumerProvider vertices,
@Nullable World world,
int light, int overlay, int seed, Operation<Void> operation) {
if (!MineLittlePony.getInstance().getRenderDispatcher().getMagicRenderer().renderItem(target, entity, stack, mode, left, matrices, vertices, world, light, overlay, seed, operation)) {
operation.call(entity, stack, mode, left, matrices, vertices, world, light, overlay, seed);
operation.call(target, entity, stack, mode, left, matrices, vertices, world, light, overlay, seed);
}
}
}

View file

@ -14,6 +14,7 @@ import java.util.function.Supplier;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.EntityPose;
@ -42,19 +43,17 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
protected final ModelPart neck;
public final RenderList helmetRenderList;
protected final RenderList neckRenderList;
public final RenderList headRenderList;
protected final RenderList bodyRenderList;
protected final RenderList vestRenderList;
protected final RenderList legsRenderList;
protected final RenderList sleevesRenderList;
protected final RenderList mainRenderList;
private final List<SubModel<? super T>> parts = new ArrayList<>();
@Deprecated
@Nullable
protected T currentState;
@ -66,10 +65,7 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
.add(withStage(BodyPart.BODY, bodyRenderList = RenderList.of(body).add(body::rotate)))
.add(withStage(BodyPart.NECK, neckRenderList = RenderList.of(neck)))
.add(withStage(BodyPart.HEAD, headRenderList = RenderList.of(head)))
.add(withStage(BodyPart.LEGS, legsRenderList = RenderList.of().add(leftArm, rightArm, leftLeg, rightLeg)))
.add(withStage(BodyPart.LEGS, sleevesRenderList = RenderList.of().add(leftSleeve, rightSleeve, leftPants, rightPants)))
.add(withStage(BodyPart.BODY, vestRenderList = RenderList.of(jacket)))
.add(withStage(BodyPart.HEAD, helmetRenderList = RenderList.of(hat)));
.add(withStage(BodyPart.LEGS, legsRenderList = RenderList.of().add(leftArm, rightArm, leftLeg, rightLeg)));
}
protected <P extends SubModel<? super T>> P addPart(P part) {
@ -88,14 +84,16 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
protected RenderList withStage(BodyPart part, RenderList action) {
return (stack, vertices, overlay, light, color) -> {
stack.push();
if (currentState != null) {
transform(currentState, part, stack);
}
action.accept(stack, vertices, overlay, light, color);
stack.pop();
};
}
@Override
public void render(MatrixStack stack, VertexConsumer vertices, int overlay, int light, int color) {
public final void render(MatrixStack stack, VertexConsumer vertices, int overlay, int light, int color) {
mainRenderList.accept(stack, vertices, overlay, light, color);
}
@ -112,13 +110,6 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
setModelVisibilities((T)entity);
setModelAngles((T)entity);
leftSleeve.copyTransform(leftArm);
rightSleeve.copyTransform(rightArm);
leftPants.copyTransform(leftLeg);
rightPants.copyTransform(rightLeg);
jacket.copyTransform(body);
hat.copyTransform(head);
}
protected void setModelVisibilities(T state) {
@ -126,6 +117,14 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
parts.forEach(part -> part.setVisible(body.visible, state));
}
@SuppressWarnings("unchecked")
public void copyTransforms(BipedEntityModel<PlayerEntityRenderState> model) {
super.copyTransforms(model);
if (model instanceof AbstractPonyModel m) {
((AbstractPonyModel<T>)m).currentState = currentState;
}
}
protected void setModelAngles(T entity) {
float pitch = entity.attributes.motionPitch * MathHelper.RADIANS_PER_DEGREE;
head.setAngles(
@ -186,7 +185,6 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
public void setHeadRotation(float animationProgress, float yaw, float pitch) {
head.yaw = yaw * MathHelper.RADIANS_PER_DEGREE;
head.pitch = pitch * MathHelper.RADIANS_PER_DEGREE;
hat.copyTransform(head);
}
/**
@ -286,7 +284,6 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
* Takes the same parameters as {@link AbstractPonyModel.setRotationAndAngles}
*/
protected void rotateLegsSwimming(T state, @Deprecated float move, @Deprecated float swing, @Deprecated float ticks) {
float lerp = state.isInPose(EntityPose.SWIMMING) ? (float)state.attributes.motionLerp : 1;
float legLeft = (MathUtil.Angles._90_DEG + MathHelper.sin((state.limbFrequency / 3) + 2 * MathHelper.PI/3) / 2) * lerp;
@ -307,8 +304,8 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
protected void rotateLegsOnGround(T state, float move, float swing, float ticks) {
float angle = MathHelper.PI * (float) Math.pow(swing, 16);
float baseRotation = move * 0.6662F; // magic number ahoy
float scale = swing / 4;
float baseRotation = state.limbFrequency * 0.6662F; // magic number ahoy
float scale = state.limbAmplitudeMultiplier / 4;
float rainboomLegLotation = state.attributes.getMainInterpolator().interpolate(
"rainboom_leg_rotation",
@ -415,10 +412,10 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
arm.pivotX -= 6 * sigma;
arm.pivotZ -= 2;
}
if (state.size == SizePreset.TALL) {
if (state.attributes.size == SizePreset.TALL) {
arm.pivotY += 1;
}
if (state.size == SizePreset.FOAL) {
if (state.attributes.size == SizePreset.FOAL) {
arm.pivotY -= 2;
}
@ -529,8 +526,10 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
@Override
public final void setArmAngle(Arm arm, MatrixStack matrices) {
super.setArmAngle(arm, matrices);
if (currentState != null) {
positionheldItem(currentState, arm, matrices);
}
}
protected void positionheldItem(T state, Arm arm, MatrixStack matrices) {
float left = arm == Arm.LEFT ? -1 : 1;
@ -584,6 +583,6 @@ public abstract class AbstractPonyModel<T extends PonyRenderState> extends Clien
neck.hidden = !head.visible;
}
PonyTransformation.forSize(state.size).transform(state.attributes, part, stack);
PonyTransformation.forSize(state.attributes.size).transform(state.attributes, part, stack);
}
}

View file

@ -39,7 +39,7 @@ public abstract class ClientPonyModel<T extends PonyRenderState> extends PlayerE
}
@Override
public <S extends PlayerEntityRenderState> ArmPose getArmPoseForSide(S state, Arm side) {
public final <S extends PlayerEntityRenderState> ArmPose getArmPoseForSide(S state, Arm side) {
return getArmPose(state, side);
}

View file

@ -26,13 +26,9 @@ public record ArmourTexture(Identifier texture, ArmourVariant variant) {
return INTERNER.intern(new ArmourTexture(texture, ArmourVariant.NORMAL));
}
public Stream<ArmourTexture> named() {
return Stream.of(legacy(texture().withPath(p -> p.replace("1", "inner").replace("2", "outer"))), this);
}
public Stream<ArmourTexture> ponify() {
if (!PonyConfig.getInstance().disablePonifiedArmour.get()) {
return Stream.of(this, modern(ResourceUtil.ponify(texture())));
return Stream.of(this, modern(texture().withPath(p -> p.replace("humanoid", "ponified"))));
}
return Stream.of(this);
}

View file

@ -4,5 +4,5 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.equipment.EquipmentModel;
public interface ArmourTextureLookup {
ArmourTexture getTexture(ItemStack stack, ArmourLayer layerType, EquipmentModel.Layer layer);
ArmourTexture getTexture(ItemStack stack, EquipmentModel.LayerType layerType, EquipmentModel.Layer layer);
}

View file

@ -39,12 +39,7 @@ public class ArmourTextureResolver implements ArmourTextureLookup, IdentifiableR
private final LoadingCache<ArmourParameters, ArmourTexture> layerCache = CacheBuilder.newBuilder()
.expireAfterAccess(30, TimeUnit.SECONDS)
.build(CacheLoader.from(parameters -> {
return Stream.of(ArmourTexture.legacy(parameters.material().textureId())).flatMap(i -> {
if (parameters.layer() == ArmourLayer.OUTER) {
return Stream.of(i, ArmourTexture.legacy(parameters.material().textureId()));
}
return Stream.of(i);
}).flatMap(i -> {
return Stream.of(ArmourTexture.legacy(parameters.textureId())).flatMap(i -> {
if (parameters.customModelId() != 0) {
return Stream.of(ArmourTexture.legacy(i.texture().withPath(p -> p.replace(".png", parameters.customModelId() + ".png"))), i);
}
@ -53,10 +48,7 @@ public class ArmourTextureResolver implements ArmourTextureLookup, IdentifiableR
}));
private Stream<ArmourTexture> performLookup(ArmourTexture id) {
List<ArmourTexture> options = Stream.of(id)
.flatMap(ArmourTexture::named)
.flatMap(ArmourTexture::ponify)
.toList();
List<ArmourTexture> options = Stream.of(id).flatMap(ArmourTexture::ponify).toList();
return options.stream().distinct()
.filter(ArmourTexture::validate)
.findFirst()
@ -84,15 +76,17 @@ public class ArmourTextureResolver implements ArmourTextureLookup, IdentifiableR
}
@Override
public ArmourTexture getTexture(ItemStack stack, ArmourLayer layer, EquipmentModel.Layer armorLayer) {
return layerCache.getUnchecked(new ArmourParameters(layer, armorLayer, getCustom(stack)));
public ArmourTexture getTexture(ItemStack stack, EquipmentModel.LayerType layerType, EquipmentModel.Layer layer) {
return layerCache.getUnchecked(new ArmourParameters(layer, layerType, getCustom(stack)));
}
private int getCustom(ItemStack stack) {
return stack.getOrDefault(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelDataComponent.DEFAULT).value();
}
private record ArmourParameters(ArmourLayer layer, EquipmentModel.Layer material, int customModelId) {
private record ArmourParameters(EquipmentModel.Layer layer, EquipmentModel.LayerType layerType, int customModelId) {
public Identifier textureId() {
return layer.getFullTextureId(layerType);
}
}
}

View file

@ -37,19 +37,21 @@ public class PonifiedEquipmentRenderer extends EquipmentRenderer {
EquipmentSlot equipmentSlot,
EquipmentModel.LayerType layerType,
Identifier modelId,
S entity,
Models<? extends PonyModel<S>> models,
ItemStack stack,
MatrixStack matrices,
VertexConsumerProvider vertexConsumers,
int light
) {
this.render(equipmentSlot, layerType, modelId, models, stack, matrices, vertexConsumers, light, null);
this.render(equipmentSlot, layerType, modelId, entity, models, stack, matrices, vertexConsumers, light, null);
}
public <S extends PonyRenderState, V extends PonyArmourModel<S>> void render(
EquipmentSlot equipmentSlot,
EquipmentModel.LayerType layerType,
Identifier modelId,
S entity,
Models<? extends PonyModel<S>> models,
ItemStack stack,
MatrixStack matrices,
@ -72,7 +74,7 @@ public class PonifiedEquipmentRenderer extends EquipmentRenderer {
int j = getDyeColor(layer, i);
if (j != 0) {
ArmourLayer armourLayer = layerType == LayerType.HUMANOID_LEGGINGS ? ArmourLayer.INNER : ArmourLayer.OUTER;
ArmourTexture armorTexture = plugin.getTextureLookup().getTexture(stack, armourLayer, layer);
ArmourTexture armorTexture = plugin.getTextureLookup().getTexture(stack, layerType, layer);
Identifier layerTexture = layer.usePlayerTexture() && texture != null
? texture
: armorTexture.texture();
@ -80,9 +82,10 @@ public class PonifiedEquipmentRenderer extends EquipmentRenderer {
VertexConsumer armorConsumer = plugin.getArmourConsumer(equipmentSlot, vertexConsumers, layerTexture, layerType);
if (armorConsumer != null) {
ArmourVariant variant = layer.usePlayerTexture() ? ArmourVariant.NORMAL : armorTexture.variant();
models.getArmourModel(stack, null, variant).ifPresent(model -> {
models.getArmourModel(stack, armourLayer, variant).ifPresent(model -> {
VertexConsumer glintConsumer = hasGlint ? plugin.getGlintConsumer(equipmentSlot, vertexConsumers, layerType) : null;
if (model.poseModel(equipmentSlot, armourLayer, models.body())) {
model.setAngles(entity);
model.render(matrices, glintConsumer != null ? VertexConsumers.union(plugin.getGlintConsumer(equipmentSlot, vertexConsumers, layerType), armorConsumer) : armorConsumer, light, OverlayTexture.DEFAULT_UV, j);
}
});

View file

@ -5,6 +5,7 @@ import net.minecraft.entity.EquipmentSlot;
import com.minelittlepony.api.model.PonyModel;
import com.minelittlepony.client.model.AbstractPonyModel;
import com.minelittlepony.client.model.ClientPonyModel;
import com.minelittlepony.client.render.entity.state.PonyRenderState;
public class PonyArmourModel<S extends PonyRenderState> extends AbstractPonyModel<S> {
@ -17,7 +18,7 @@ public class PonyArmourModel<S extends PonyRenderState> extends AbstractPonyMode
if (!setVisibilities(slot, layer)) {
return false;
}
if (mainModel instanceof AbstractPonyModel abs) {
if (mainModel instanceof ClientPonyModel abs) {
abs.copyTransforms(this);
}
return true;

View file

@ -1,9 +1,9 @@
package com.minelittlepony.client.model.entity;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.util.math.MatrixStack;
import com.minelittlepony.api.model.BodyPart;
import com.minelittlepony.client.render.entity.EnderStallionRenderer;
public class EnderStallionModel extends SkeleponyModel<EnderStallionRenderer.State> {
@ -36,11 +36,9 @@ public class EnderStallionModel extends SkeleponyModel<EnderStallionRenderer.Sta
}
@Override
public void render(MatrixStack stack, VertexConsumer vertices, int overlay, int light, int color) {
stack.push();
public void transform(EnderStallionRenderer.State state, BodyPart part, MatrixStack stack) {
stack.translate(0, -1.15F, 0);
super.render(stack, vertices, overlay, light, color);
stack.pop();
super.transform(state, part, stack);
}
@Override

View file

@ -13,8 +13,6 @@ import com.minelittlepony.client.render.entity.SkeleponyRenderer;
public class SkeleponyModel<T extends SkeleponyRenderer.State> extends AlicornModel<T> {
public SkeleponyModel(ModelPart tree) {
super(tree, false);
vestRenderList.clear();
sleevesRenderList.clear();
}
@SuppressWarnings("unchecked")

View file

@ -20,7 +20,7 @@ public class AlicornModel<T extends PonyRenderState> extends UnicornModel<T> imp
public void init(ModelView context) {
super.init(context);
wings = addPart(context.findByName("wings"));
bodyRenderList.add(forPart(this::getWings).checked(() -> currentState.race.hasWings()));
bodyRenderList.add(forPart(this::getWings));
}
@Override

View file

@ -9,6 +9,7 @@ import com.minelittlepony.mson.api.ModelView;
import com.minelittlepony.mson.util.RenderList;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.consume.UseAction;
import net.minecraft.registry.Registries;
@ -17,7 +18,7 @@ import net.minecraft.util.*;
/**
* Used for both unicorns and alicorns since there's no logical way to keep them distinct and not duplicate stuff.
*/
public class UnicornModel<T extends PonyRenderState> extends EarthPonyModel<T> implements HornedPonyModel<T> {
public class UnicornModel<T extends PonyRenderState> extends EarthPonyModel<T> {
protected final ModelPart unicornArmRight;
protected final ModelPart unicornArmLeft;
@ -30,14 +31,24 @@ public class UnicornModel<T extends PonyRenderState> extends EarthPonyModel<T> i
unicornArmLeft = tree.getChild("left_cast");
}
public boolean isCasting(T state) {
return state instanceof PlayerEntityRenderState s
&& (getArmPose(s, Arm.LEFT) != ArmPose.EMPTY || getArmPose(s, Arm.RIGHT) != ArmPose.EMPTY);
}
@Override
public float getWobbleAmplitude(T state) {
return isCasting(state) ? 0 : 1;
}
@Override
public void init(ModelView context) {
super.init(context);
horn = addPart(context.findByName("horn"));
headRenderList.add(RenderList.of().add(head::rotate).add(forPart(horn)).checked(() -> currentState.race.hasHorn()));
this.mainRenderList.add(withStage(BodyPart.HEAD, RenderList.of().add(head::rotate).add((stack, vertices, overlay, light, color) -> {
horn.renderMagic(stack, vertices, currentState.attributes.metadata.glowColor());
})).checked(() -> currentState.hasMagicGlow() && isCasting(currentState)));
headRenderList.add(RenderList.of().add(head::rotate).add(forPart(horn)));
mainRenderList.add(withStage(BodyPart.HEAD, RenderList.of().add(head::rotate).add((stack, vertices, overlay, light, color) -> {
horn.renderMagic(stack, vertices, currentState == null ? 0 : currentState.attributes.metadata.glowColor());
})).checked(() -> isCasting(currentState)));
}
@Override
@ -60,7 +71,7 @@ public class UnicornModel<T extends PonyRenderState> extends EarthPonyModel<T> i
@Override
public ModelPart getArm(Arm side) {
if (currentState.hasMagicGlow() && getArmPoseForSide(currentState, side) != ArmPose.EMPTY && PonyConfig.getInstance().tpsmagic.get()) {
if (currentState != null && currentState.hasMagicGlow() && getArmPoseForSide(currentState, side) != ArmPose.EMPTY && PonyConfig.getInstance().tpsmagic.get()) {
return side == Arm.LEFT ? unicornArmLeft : unicornArmRight;
}
return super.getArm(side);
@ -70,7 +81,7 @@ public class UnicornModel<T extends PonyRenderState> extends EarthPonyModel<T> i
protected void positionheldItem(T state, Arm arm, MatrixStack matrices) {
super.positionheldItem(state, arm, matrices);
if (!PonyConfig.getInstance().tpsmagic.get() || !currentState.hasMagicGlow()) {
if (!PonyConfig.getInstance().tpsmagic.get() || !state.hasMagicGlow()) {
return;
}
@ -93,9 +104,9 @@ public class UnicornModel<T extends PonyRenderState> extends EarthPonyModel<T> i
float x = 0.3F;
float z = -0.4F;
if (state.size == SizePreset.TALL || state.size == SizePreset.YEARLING) {
if (state.attributes.size == SizePreset.TALL || state.attributes.size == SizePreset.YEARLING) {
z += 0.05F;
} else if (state.size == SizePreset.FOAL) {
} else if (state.attributes.size == SizePreset.FOAL) {
x -= 0.1F;
z -= 0.1F;
}

View file

@ -112,7 +112,7 @@ public class PonyTail implements SubModel<PonyRenderState>, MsonModel {
}
public void setAngles(int index, PonyTail tail, ModelAttributes attributes) {
tree.visible = index >= tail.tailStop;
tree.visible = index < tail.tailStop;
shape = tail.shape;
horsey = attributes.isHorsey;

View file

@ -21,6 +21,8 @@ public class PonyWings<S extends PonyRenderState> implements SubModel<S>, MsonMo
protected Wing<S> legacyWing;
private boolean visible;
public PonyWings(ModelPart tree) {
}
@ -113,6 +115,10 @@ public class PonyWings<S extends PonyRenderState> implements SubModel<S>, MsonMo
}
}
@Override
public void setVisible(boolean visible, S state) {
visible = state.race.hasWings();
}
private boolean isBurdened(S state) {
return state.getAttributes().isWearing(Wearable.SADDLE_BAGS_BOTH)
@ -122,10 +128,12 @@ public class PonyWings<S extends PonyRenderState> implements SubModel<S>, MsonMo
@Override
public void renderPart(MatrixStack stack, VertexConsumer vertices, int overlay, int light, int color) {
if (visible) {
leftWing.render(stack, vertices, overlay, light, color);
rightWing.render(stack, vertices, overlay, light, color);
legacyWing.render(stack, vertices, overlay, light, color);
}
}
public static class Wing<S extends PonyRenderState> implements MsonModel {

View file

@ -17,6 +17,7 @@ public class UnicornHorn<T extends PonyRenderState> implements SubModel<T> {
private final ModelPart glow;
protected boolean visible = true;
protected boolean glowing;
public UnicornHorn(ModelPart tree) {
horn = tree.getChild("bone");
@ -39,7 +40,7 @@ public class UnicornHorn<T extends PonyRenderState> implements SubModel<T> {
@Override
public void setVisible(boolean visible, T state) {
horn.visible = this.visible && visible;
glow.visible = this.visible && visible;
horn.visible = this.visible && visible && state.race.hasHorn();
glow.visible = this.visible && visible && state.hasMagicGlow();
}
}

View file

@ -37,7 +37,7 @@ public final class DebugBoundingBoxRenderer {
}
public static Box getBoundingBox(PonyRenderState state) {
return getBoundingBox(state.x, state.y, state.z, state.size.scaleFactor(), state.width, state.height);
return getBoundingBox(state.x, state.y, state.z, state.attributes.size.scaleFactor(), state.width, state.height);
}
public static Box getBoundingBox(double x, double y, double z, float scale, float width, float height) {

View file

@ -94,7 +94,7 @@ public class EquineRenderManager<
}
public void setupTransforms(S state, MatrixStack stack, float animationProgress, float bodyYaw) {
float s = state.size.scaleFactor();
float s = state.attributes.size.scaleFactor();
stack.scale(s, s, s);
if (state instanceof PlayerEntityRenderState && state.attributes.isSitting) {

View file

@ -37,8 +37,8 @@ public class LevitatingItemRenderer {
* Renders an item with a magical overlay.
*/
public boolean renderItem(ItemRenderer itemRenderer, @Nullable LivingEntity entity, ItemStack stack, ModelTransformationMode mode, boolean left,
MatrixStack matrix, VertexConsumerProvider renderContext, @Nullable World world,
int lightUv, int overlay, int seed, Operation<Void> original) {
MatrixStack matrices, VertexConsumerProvider vertices, @Nullable World world,
int light, int overlay, int seed, Operation<Void> original) {
if (entity == null || !(mode.isFirstPerson()
|| mode == ModelTransformationMode.THIRD_PERSON_LEFT_HAND
@ -54,18 +54,18 @@ public class LevitatingItemRenderer {
var state = context.getAndUpdateRenderState(entity, MinecraftClient.getInstance().getRenderTickCounter().getTickDelta(false));
matrix.push();
matrices.push();
boolean doMagic = (mode.isFirstPerson() ? PonyConfig.getInstance().fpsmagic : PonyConfig.getInstance().tpsmagic).get() && state.hasMagicGlow();
if (doMagic && mode.isFirstPerson()) {
setupPerspective(entity, stack, left, matrix);
setupPerspective(entity, stack, left, matrices);
}
original.call(entity, stack, mode, left, matrix, renderContext, world, lightUv, overlay, seed);
original.call(itemRenderer, entity, stack, mode, left, matrices, vertices, world, light, overlay, seed);
if (doMagic) {
VertexConsumerProvider interceptedContext = getProvider(state.pony, renderContext);
VertexConsumerProvider interceptedContext = getProvider(state.pony, vertices);
if (stack.hasGlint()) {
stack = stack.copy();
@ -80,16 +80,16 @@ public class LevitatingItemRenderer {
float zDrift = MathHelper.cos((tickDelta + 20) / 20F) * driftStrength;
float scale = 1.1F + (MathHelper.sin(tickDelta / 20F) + 1) * driftStrength;
matrix.scale(scale, scale, scale);
matrix.translate(0.015F + xDrift, 0.01F, 0.01F + zDrift);
matrices.scale(scale, scale, scale);
matrices.translate(0.015F + xDrift, 0.01F, 0.01F + zDrift);
original.call(entity, stack, mode, left, matrix, interceptedContext, world, lightUv, OverlayTexture.DEFAULT_UV, seed);
matrix.scale(scale, scale, scale);
matrix.translate(-0.03F - xDrift, -0.02F, -0.02F - zDrift);
original.call(entity, stack, mode, left, matrix, interceptedContext, world, lightUv, OverlayTexture.DEFAULT_UV, seed);
original.call(itemRenderer, entity, stack, mode, left, matrices, interceptedContext, world, light, OverlayTexture.DEFAULT_UV, seed);
matrices.scale(scale, scale, scale);
matrices.translate(-0.03F - xDrift, -0.02F, -0.02F - zDrift);
original.call(itemRenderer, entity, stack, mode, left, matrices, interceptedContext, world, light, OverlayTexture.DEFAULT_UV, seed);
}
matrix.pop();
matrices.pop();
return true;
}

View file

@ -6,6 +6,7 @@ import com.minelittlepony.api.pony.Pony;
import com.minelittlepony.client.model.AbstractPonyModel;
import com.minelittlepony.client.render.MobRenderers;
import com.minelittlepony.client.render.blockentity.skull.PonySkullRenderer.ISkull;
import com.minelittlepony.client.render.entity.state.PonyRenderState;
import com.minelittlepony.mson.api.ModelKey;
import java.util.function.Supplier;
@ -20,15 +21,17 @@ import net.minecraft.util.math.RotationAxis;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
public class MobSkull implements ISkull {
public class MobSkull<S extends PonyRenderState> implements ISkull {
private final Identifier texture;
private final MobRenderers type;
private final Supplier<AbstractPonyModel<?>> ponyHead;
private final Supplier<S> state;
MobSkull(Identifier texture, MobRenderers type, ModelKey<? extends AbstractPonyModel<?>> modelKey) {
MobSkull(Identifier texture, MobRenderers type, ModelKey<? extends AbstractPonyModel<?>> modelKey, Supplier<S> state) {
this.texture = texture;
this.type = type;
this.state = Suppliers.memoize(state::get);
this.ponyHead = Suppliers.memoize(modelKey::createModel);
}
@ -44,6 +47,11 @@ public class MobSkull implements ISkull {
@Override
public boolean bindPony(Pony pony) {
S state = this.state.get();
state.pony = pony;
state.race = pony.race();
state.attributes.size = pony.size();
state.attributes.metadata = pony.metadata();
return true;
}
@ -51,11 +59,12 @@ public class MobSkull implements ISkull {
public void setAngles(float yaw, float animationProgress) {
Vector3f v = new Vector3f(0, -2, 1.99F);
v.rotate(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
ponyHead.get().setVisible(true);
ponyHead.get().setAngles(state.get());
ModelPart head = ponyHead.get().getHead();
head.pivotX = v.x;
head.pivotY = v.y;
head.pivotZ = v.z;
ponyHead.get().setVisible(true);
ponyHead.get().setHeadRotation(animationProgress, yaw, 0);
}

View file

@ -83,9 +83,6 @@ public class PlayerPonySkull implements ISkull {
stack.push();
ponyHead.headRenderList.accept(stack, vertices, light, overlay, color);
stack.pop();
stack.push();
ponyHead.helmetRenderList.accept(stack, vertices, light, overlay, color);
stack.pop();
if (renderingEars) {
stack.push();
stack.scale(1.3333334f, 1.3333334f, 1.3333334f);

View file

@ -6,6 +6,7 @@ import com.minelittlepony.client.model.ModelType;
import com.minelittlepony.client.model.armour.ArmourRendererPlugin;
import com.minelittlepony.client.render.MobRenderers;
import com.minelittlepony.client.render.entity.*;
import com.minelittlepony.client.render.entity.state.PonyRenderState;
import net.minecraft.block.AbstractSkullBlock;
import net.minecraft.block.SkullBlock;
@ -51,10 +52,10 @@ public class PonySkullRenderer {
public void reload() {
skulls = Util.make(new HashMap<>(), skullMap -> {
skullMap.put(SkullBlock.Type.SKELETON, new MobSkull(SkeleponyRenderer.SKELETON, MobRenderers.SKELETON, ModelType.SKELETON));
skullMap.put(SkullBlock.Type.WITHER_SKELETON, new MobSkull(SkeleponyRenderer.WITHER, MobRenderers.SKELETON, ModelType.SKELETON));
skullMap.put(SkullBlock.Type.ZOMBIE, new MobSkull(ZomponyRenderer.ZOMBIE, MobRenderers.ZOMBIE, ModelType.ZOMBIE));
skullMap.put(SkullBlock.Type.PIGLIN, new MobSkull(PonyPiglinRenderer.PIGLIN, MobRenderers.PIGLIN, ModelType.PIGLIN));
skullMap.put(SkullBlock.Type.SKELETON, new MobSkull<>(SkeleponyRenderer.SKELETON, MobRenderers.SKELETON, ModelType.SKELETON, SkeleponyRenderer.State::new));
skullMap.put(SkullBlock.Type.WITHER_SKELETON, new MobSkull<>(SkeleponyRenderer.WITHER, MobRenderers.SKELETON, ModelType.SKELETON, SkeleponyRenderer.State::new));
skullMap.put(SkullBlock.Type.ZOMBIE, new MobSkull<>(ZomponyRenderer.ZOMBIE, MobRenderers.ZOMBIE, ModelType.ZOMBIE, PonyRenderState::new));
skullMap.put(SkullBlock.Type.PIGLIN, new MobSkull<>(PonyPiglinRenderer.PIGLIN, MobRenderers.PIGLIN, ModelType.PIGLIN, PonyPiglinRenderer.State::new));
skullMap.put(SkullBlock.Type.PLAYER, new PlayerPonySkull());
});
headModels = SkullBlockEntityRenderer.getModels(MinecraftClient.getInstance().getEntityModelLoader());

View file

@ -116,7 +116,7 @@ public abstract class AbstractPonyRenderer<
@Override
public void scale(S state, MatrixStack stack) {
shadowRadius = state.size.shadowSize();
shadowRadius = state.attributes.size.shadowSize();
if (state.baby) {
shadowRadius *= 3; // undo vanilla shadow scaling

View file

@ -73,7 +73,7 @@ public class PlayerPonyRenderer
public Vec3d getPositionOffset(PlayerEntityRenderState state) {
Vec3d offset = super.getPositionOffset(state);
return offset.add(state.baseScale * ((PlayerPonyRenderState)state).yOffset).multiply(((PonyRenderState)state).size.scaleFactor());
return offset.add(state.baseScale * ((PlayerPonyRenderState)state).yOffset).multiply(((PonyRenderState)state).attributes.size.scaleFactor());
}
@Override
@ -102,7 +102,7 @@ public class PlayerPonyRenderer
@Override
public void render(PlayerEntityRenderState state, MatrixStack stack, VertexConsumerProvider vertices, int light) {
// EntityModelFeatures: We have to force it to use our models otherwise EMF overrides it and breaks pony rendering
shadowRadius = ((PlayerPonyRenderState)state).size.shadowSize();
shadowRadius = ((PlayerPonyRenderState)state).attributes.size.shadowSize();
super.render(state, stack, vertices, light);
DebugBoundingBoxRenderer.render((PlayerPonyRenderState)state, stack, vertices);

View file

@ -45,6 +45,6 @@ public class PonyPiglinRenderer extends PonyRenderer<HostileEntity, PonyPiglinRe
public static class State extends PonyRenderState {
public boolean zombified;
public PiglinActivity activity;
public PiglinActivity activity = PiglinActivity.DEFAULT;
}
}

View file

@ -50,7 +50,7 @@ public class SeaponyRenderer extends PonyRenderer<GuardianEntity, SeaponyRendere
super.updateRenderState(entity, state, tickDelta);
state.spikesExtension = entity.getSpikesExtension(tickDelta);
state.tailAngle = entity.getTailAngle(tickDelta);
state.cameraPosVec = getScaledCameraPosVec(entity, tickDelta, state.size.scaleFactor());
state.cameraPosVec = getScaledCameraPosVec(entity, tickDelta, state.attributes.size.scaleFactor());
Entity cameraBeamTarget = GuardianEntityRenderer.getBeamTarget(entity);
state.rotationVec = cameraBeamTarget != null ? entity.getRotationVec(tickDelta) : null;
state.lookAtPos = cameraBeamTarget != null ? cameraBeamTarget.getCameraPosVec(tickDelta) : null;

View file

@ -65,7 +65,7 @@ public class ArmourFeature<
? EquipmentModel.LayerType.HUMANOID_LEGGINGS
: EquipmentModel.LayerType.HUMANOID;
Identifier modelId = equippableComponent.model().orElseThrow();
equipmentRenderer.render(armorSlot, layerType, modelId, models, stack, matrices, vertices, light);
equipmentRenderer.render(armorSlot, layerType, modelId, entity, models, stack, matrices, vertices, light);
}
}

View file

@ -13,7 +13,7 @@ import com.minelittlepony.api.config.PonyConfig;
import com.minelittlepony.api.events.PonyModelPrepareCallback;
import com.minelittlepony.api.model.ModelAttributes;
import com.minelittlepony.api.model.PonyModel;
import com.minelittlepony.api.pony.Pony;
import com.minelittlepony.api.pony.*;
import com.minelittlepony.api.pony.meta.*;
import com.minelittlepony.client.transform.PonyPosture;
@ -31,15 +31,14 @@ public class PonyRenderState extends PlayerEntityRenderState implements PonyMode
public boolean onGround;
public boolean isTechnoblade;
public Pony pony;
public Size size;
public Race race;
public Pony pony = Pony.getManager().getPony(DefaultPonySkinHelper.STEVE);
public Race race = Race.HUMAN;
public void updateState(LivingEntity entity, PonyModel<?> model, Pony pony, ModelAttributes.Mode mode) {
this.pony = pony;
attributes.updateLivingState(entity, pony, mode);
attributes.checkRainboom(entity, model, age);
size = baby ? SizePreset.FOAL : PonyConfig.getEffectiveSize(attributes.metadata.size());
baby = attributes.size == SizePreset.FOAL;
race = PonyConfig.getEffectiveRace(attributes.metadata.race());
vehicleOffset = hasVehicle ? entity.getVehicle().getEyeHeight(pose) : 0;
riderOffset = getRiderYOffset();
@ -94,7 +93,7 @@ public class PonyRenderState extends PlayerEntityRenderState implements PonyMode
* Gets the y-offset applied to entities riding this one.
*/
protected float getRiderYOffset() {
return switch ((SizePreset)size) {
return switch ((SizePreset)attributes.size) {
case NORMAL -> 0.4F;
default -> 0.25F;
};
@ -112,7 +111,7 @@ public class PonyRenderState extends PlayerEntityRenderState implements PonyMode
float y = -(height + 0.5F);
// Then we add our own offsets.
y += attributes.visualHeight * size.scaleFactor() + 0.25F;
y += attributes.visualHeight * attributes.size.scaleFactor() + 0.25F;
y += vehicleOffset;
if (isInSneakingPose) {

View file

@ -114,7 +114,7 @@ public class NativeUtil {
Resource res = mc.getResourceManager().getResource(resource).orElse(null);
if (res != null) {
try (InputStream inputStream = res.getInputStream()){
try (InputStream inputStream = res.getInputStream()) {
consumer.accept(NativeImage.read(inputStream)::getColorArgb);
return;
}

View file

@ -3,8 +3,6 @@ package com.minelittlepony.util;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
import com.minelittlepony.client.MineLittlePony;
import java.util.Optional;
public final class ResourceUtil {
@ -30,16 +28,4 @@ public final class ResourceUtil {
public static Optional<Identifier> verifyTexture(Identifier texture) {
return textureExists(texture) ? Optional.of(texture) : Optional.empty();
}
public static Identifier ponify(Identifier texture) {
String path = texture.getPath();
if (path.endsWith("_pony.png")) {
return texture;
}
if (Identifier.DEFAULT_NAMESPACE.contentEquals(texture.getNamespace())) {
return MineLittlePony.id(path.replace(".png", "_pony.png")); // it's in the vanilla namespace, we provide these.
}
return texture.withPath(p -> p.replace(".png", "_pony.png"));
}
}

View file

@ -22,7 +22,6 @@
},
"hat": {
"texture": { "u": 40, "v": 27 },
"pivot": [0, 1, -4],
"children": {
"hat_parts": {
"pivot": [0, 2, 0],

View file

@ -5,7 +5,12 @@
"texture": {"u": 16, "v": 8},
"cubes": [
{"from": [-4, 4, -2], "size": [8, 8, 16]}
]
],
"children": {
"jacket": {
"visible": false
}
}
},
"right_leg": {
"pivot": ["#arm_rotation_x_neg", 0, 0],
@ -15,7 +20,12 @@
"from": [ "#arm_x_neg", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ]
}
]
],
"children": {
"right_pants": {
"visible": false
}
}
},
"left_leg": {
"pivot": ["#arm_rotation_x", 0, 0],
@ -26,7 +36,12 @@
"from": [ "#arm_x", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ]
}
]
],
"children": {
"left_pants": {
"visible": false
}
}
}
}
}

View file

@ -17,6 +17,9 @@
"south": [-4, 4, 10, 8, 8, 32, 23],
"up": [ -4, 4, 1, 8, 12, 32, 23],
"__comment": "it's a little short, so the butt tends to show. :/"
},
"jacket": {
"visible": false
}
}
},
@ -25,7 +28,17 @@
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": 0.5 },
{ "from": [-4, -8, -1], "size": [ 2, 2, 2], "texture": {"u": 0, "v": 0}, "dilate": -0.0125 },
{ "from": [ 2, -8, -1], "size": [ 2, 2, 2], "texture": {"u": 0, "v": 4}, "dilate": -0.0125 }
],
"children": {
"hat": {
"texture": { "u": 32, "v": 0 },
"dilate": ["#head_elongation", "#head_elongation", 0],
"pivot": [ 0, "#head_pivot_y", 0 ],
"cubes": [
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": 0.5 }
]
}
}
},
"right_arm": {
"pivot": ["#arm_rotation_x_neg", "#arm_rotation_y", 0],
@ -35,7 +48,12 @@
"from": [ "#arm_x_neg", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ]
}
]
],
"children": {
"right_sleeve": {
"visible": false
}
}
},
"left_arm": {
"pivot": ["#arm_rotation_x", "#arm_rotation_y", 0],
@ -46,7 +64,12 @@
"from": [ "#arm_x", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ]
}
]
],
"children": {
"left_sleeve": {
"visible": false
}
}
},
"right_leg": {
"pivot": ["#arm_rotation_x_neg", 0, 0],
@ -56,7 +79,12 @@
"from": [ "#arm_x_neg", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ]
}
]
],
"children": {
"right_pants": {
"visible": false
}
}
},
"left_leg": {
"pivot": ["#arm_rotation_x", 0, 0],
@ -67,7 +95,12 @@
"from": [ "#arm_x", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ]
}
]
],
"children": {
"left_pants": {
"visible": false
}
}
}
}
}

View file

@ -7,7 +7,12 @@
"dilate": 0.41,
"cubes": [
{"from": [-4, 4, -2], "size": [8, 8, 16]}
]
],
"children": {
"jacket": {
"visible": false
}
}
}
}
}

View file

@ -8,7 +8,6 @@
],
"children": {
"hat": {
"pivot": [ 0, 5, 0 ],
"cubes": [
{ "from": [ -1, -7, -1 ], "size": [ 2, 7, 2 ], "dilate": 0.5 }
]

View file

@ -29,7 +29,6 @@
"hat": {
"texture": { "u": 32, "v": 0 },
"visible": false,
"pivot": [0, 0, -2],
"cubes": [
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": -0.5 }
]

View file

@ -29,7 +29,6 @@
"hat": {
"texture": { "u": 32, "v": 0 },
"dilate": ["#head_elongation", "#head_elongation", 0],
"pivot": [ 0, "#head_pivot_y", 0 ],
"cubes": [
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": 0.5 }
]

View file

@ -50,7 +50,6 @@
}
},
"left_sleeve": {
"pivot": ["#arm_rotation_x", "#arm_rotation_y", "#arm_rotation_z"],
"visible": false,
"texture": { "u": 48, "v": 48 },
"cubes": [
@ -108,7 +107,11 @@
"visible": false,
"texture": { "u": 40, "v": 32 },
"cubes": [
{ "from": [-3, -2, -2], "size": [ 4, 12, 4], "dilate": [0.25, 0.25, 0.25] }
{
"from": [ "#arm_x_neg", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ],
"dilate": 0.25
}
]
}
}

View file

@ -77,7 +77,6 @@
}
},
"left_sleeve": {
"pivot": ["#arm_rotation_x", "#arm_rotation_y", "#arm_rotation_z"],
"visible": false,
"texture": { "u": 48, "v": 48 },
"cubes": [
@ -115,6 +114,17 @@
"height": "#fore_arm_length",
"depth": "#arm_depth"
}
},
"right_sleeve": {
"visible": false,
"texture": { "u": 40, "v": 32 },
"cubes": [
{
"from": [ "#arm_x_neg", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ],
"dilate": 0.25
}
]
}
}
},
@ -143,6 +153,17 @@
"height": "#fore_leg_length",
"depth": "#arm_depth"
}
},
"left_pants": {
"visible": false,
"texture": { "u": 0, "v": 48 },
"cubes": [
{
"from": [ "#arm_x", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ],
"dilate": 0.25
}
]
}
}
},
@ -172,11 +193,15 @@
"depth": "#arm_depth"
}
},
"right_sleeve": {
"right_pants": {
"visible": false,
"texture": { "u": 40, "v": 32 },
"texture": { "u": 0, "v": 32 },
"cubes": [
{ "from": [-3, -2, -2], "size": [ 4, 12, 4], "dilate": [0.25, 0.25, 0.25] }
{
"from": [ "#arm_x_neg", 4, "#arm_z"],
"size": [ "#arm_width", "#arm_length", "#arm_depth" ],
"dilate": 0.25
}
]
}
}

View file

@ -38,7 +38,6 @@
"hat": {
"texture": { "u": 32, "v": 0 },
"dilate": ["#head_elongation", "#head_elongation", 0],
"pivot": [ 0, "#head_pivot_y", 0 ],
"cubes": [
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": 0.5 }
]

View file

@ -31,7 +31,6 @@
"hat": {
"texture": { "u": 32, "v": 0 },
"dilate": ["#head_elongation", "#head_elongation", 0],
"pivot": [ 0, "#head_pivot_y", 0 ],
"cubes": [
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": 0.5 }
]

View file

@ -56,7 +56,6 @@
],
"children": {
"right_sleeve": {
"pivot": ["#arm_rotation_x_neg", "#arm_rotation_y", "#arm_rotation_z"],
"rotate": [-80, 29, 0],
"visible": false,
"texture": { "u": 40, "v": 32 },
@ -82,7 +81,6 @@
],
"children": {
"left_sleeve": {
"pivot": ["#arm_rotation_x", "#arm_rotation_y", "#arm_rotation_z"],
"rotate": [-80, -29, 0],
"visible": false,
"texture": { "u": 48, "v": 48 },

View file

@ -26,7 +26,6 @@
"hat": {
"texture": { "u": 32, "v": 0 },
"dilate": ["#head_elongation", "#head_elongation", 0],
"pivot": [ 0, "#head_pivot_y", 0 ],
"cubes": [
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": 0.5 }
]

View file

@ -28,7 +28,6 @@
"hat": {
"texture": { "u": 32, "v": 0 },
"dilate": ["#head_elongation", "#head_elongation", 0],
"pivot": [ 0, "#head_pivot_y", 0 ],
"cubes": [
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": 0.5 }
]

View file

@ -278,7 +278,6 @@
],
"children": {
"right_sleeve": {
"pivot": ["#arm_rotation_x_neg", "#arm_rotation_y", "#arm_rotation_z"],
"visible": false,
"texture": { "u": 40, "v": 32 },
"cubes": [
@ -302,7 +301,6 @@
],
"children": {
"left_sleeve": {
"pivot": ["#arm_rotation_x", "#arm_rotation_y", "#arm_rotation_z"],
"visible": false,
"texture": { "u": 48, "v": 48 },
"cubes": [
@ -326,7 +324,6 @@
],
"children": {
"right_pants": {
"pivot": ["#arm_rotation_x_neg", "#arm_rotation_y", 11],
"visible": false,
"texture": { "u": 0, "v": 32 },
"cubes": [
@ -350,7 +347,6 @@
],
"children": {
"left_pants": {
"pivot": ["#arm_rotation_x_neg", "#arm_rotation_y", 11],
"visible": false,
"texture": { "u": 0, "v": 48 },
"cubes": [

View file

@ -26,7 +26,6 @@
"hat": {
"texture": { "u": 32, "v": 0 },
"dilate": ["#head_elongation", "#head_elongation", 0],
"pivot": [ 0, "#head_pivot_y", 0 ],
"cubes": [
{ "from": [-4, -6, -6], "size": [ 8, 8, 8], "dilate": 0.5 }
]