1
0
mirror of https://github.com/GeyserMC/Rainbow.git synced 2025-12-19 14:59:16 +00:00

Proper 3rd-person animation mapping

This commit is contained in:
Eclipse
2025-07-06 09:29:44 +00:00
parent c109d1ef0d
commit 3cb91d3d64
5 changed files with 43 additions and 24 deletions

View File

@@ -94,13 +94,13 @@ public class BedrockItemMapper {
ResourceLocation texture = itemModelLocation;
Material layer0Texture = itemModel.getTopTextureSlots().getMaterial("layer0");
Optional<SimpleUnbakedGeometry> customGeometry = Optional.empty();
Optional<ResolvedModel> customGeometry = Optional.empty();
if (layer0Texture != null) {
texture = layer0Texture.texture();
} else {
// Unknown texture (doesn't use layer0), so we immediately assume the geometry is custom
// This check should probably be done differently
customGeometry = Optional.of((SimpleUnbakedGeometry) itemModel.getTopGeometry());
customGeometry = Optional.of(itemModel);
}
context.create(bedrockIdentifier, texture, handheld, customGeometry);
}, () -> context.reporter.report(() -> "missing block model " + itemModelLocation));
@@ -169,7 +169,7 @@ public class BedrockItemMapper {
}
public void create(ResourceLocation bedrockIdentifier, ResourceLocation texture, boolean displayHandheld,
Optional<SimpleUnbakedGeometry> customGeometry) {
Optional<ResolvedModel> customModel) {
GeyserSingleDefinition definition = new GeyserSingleDefinition(Optional.of(model), bedrockIdentifier, Optional.of(displayName), predicateStack,
new GeyserSingleDefinition.BedrockOptions(Optional.empty(), true, displayHandheld, protectionValue), componentPatch);
try {
@@ -180,14 +180,10 @@ public class BedrockItemMapper {
}
// TODO Should probably get a better way to get geometry texture
Optional<BedrockGeometry> bedrockGeometry = customGeometry.map(geometry -> GeometryMapper.mapGeometry(definition.textureName(), geometry));
Optional<BedrockGeometryContext> geometryContext = bedrockGeometry.map(geometry -> new BedrockGeometryContext(geometry.definitions().getFirst(), texture));
Optional<BedrockAnimationContext> animationContext = geometryContext.map(
geometry -> AnimationMapper.mapAnimation(definition.textureName(), geometry.geometry().bones().getFirst().name(), null));
Optional<BedrockGeometryContext> bedrockGeometry = customModel.map(model -> GeometryMapper.mapGeometry(definition.textureName(), model, texture));
itemConsumer.accept(new BedrockItem(bedrockIdentifier, definition.textureName(), texture,
AttachableMapper.mapItem(componentPatch, bedrockIdentifier, geometryContext, animationContext, additionalTextureConsumer),
bedrockGeometry, animationContext.map(BedrockAnimationContext::animation)));
AttachableMapper.mapItem(componentPatch, bedrockIdentifier, bedrockGeometry, additionalTextureConsumer), bedrockGeometry.map(BedrockGeometryContext::geometry),
bedrockGeometry.map(BedrockGeometryContext::animation).map(BedrockAnimationContext::animation)));
}
}
}

View File

@@ -1,17 +1,26 @@
package org.geysermc.packgenerator.mapping.animation;
import net.minecraft.client.renderer.block.model.ItemTransform;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import org.geysermc.packgenerator.pack.animation.BedrockAnimation;
import org.joml.Vector3f;
import org.joml.Vector3fc;
public class AnimationMapper {
private static final Vector3fc POSITION_OFFSET = new Vector3f(0.0F, 13.0F, -3.0F);
private static final Vector3fc ROTATION_OFFSET = new Vector3f(90.0F, -90.0F, 0.0F);
public static BedrockAnimationContext mapAnimation(String identifier, String bone, ItemTransforms transforms) {
ItemTransform thirdPerson = transforms.thirdPersonLeftHand();
Vector3f thirdPersonPosition = POSITION_OFFSET.add(thirdPerson.translation(), new Vector3f());
Vector3f thirdPersonRotation = ROTATION_OFFSET.add(-thirdPerson.rotation().x(), thirdPerson.rotation().y(), thirdPerson.rotation().z(), new Vector3f());
Vector3f thirdPersonScale = new Vector3f(thirdPerson.scale());
return new BedrockAnimationContext(BedrockAnimation.builder()
.withAnimation(identifier + ".hold_first_person", BedrockAnimation.animation())
.withAnimation(identifier + ".hold_third_person", BedrockAnimation.animation()
.withLoopMode(BedrockAnimation.LoopMode.LOOP)
.withBone(bone, new Vector3f(0.0F, 40.0F, 0.0F), new Vector3f(), new Vector3f()))
.withBone(bone, thirdPersonPosition, thirdPersonRotation, thirdPersonScale))
.build(), "animation." + identifier + ".hold_first_person", "animation." + identifier + ".hold_third_person");
}
}

View File

@@ -9,7 +9,6 @@ import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.equipment.Equippable;
import org.geysermc.packgenerator.mapping.animation.BedrockAnimationContext;
import org.geysermc.packgenerator.mapping.geometry.BedrockGeometryContext;
import org.geysermc.packgenerator.mixin.EntityRenderDispatcherAccessor;
import org.geysermc.packgenerator.pack.attachable.BedrockAttachable;
@@ -21,11 +20,11 @@ import java.util.function.Consumer;
public class AttachableMapper {
public static Optional<BedrockAttachable> mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, Optional<BedrockGeometryContext> customGeometry,
Optional<BedrockAnimationContext> animations, Consumer<ResourceLocation> textureConsumer) {
Consumer<ResourceLocation> textureConsumer) {
// Crazy optional statement
// Unfortunately we can't have both equippables and custom models, so we prefer the latter :(
return customGeometry
.map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.geometry(), geometry.texture().getPath()))
.map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.geometry().definitions().getFirst(), geometry.texture().getPath()))
.or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE))
.flatMap(optional -> (Optional<Equippable>) optional)
.flatMap(equippable -> {
@@ -42,7 +41,7 @@ public class AttachableMapper {
return BedrockAttachable.equipment(bedrockIdentifier, assetInfo.getFirst(), texture.getPath());
}))
.map(attachable -> {
animations.ifPresent(context -> {
customGeometry.map(BedrockGeometryContext::animation).ifPresent(context -> {
attachable.withAnimation("first_person", context.firstPerson());
attachable.withAnimation("third_person", context.thirdPerson());
attachable.withScript("animate", "first_person", "context.is_first_person == 1.0");

View File

@@ -1,6 +1,7 @@
package org.geysermc.packgenerator.mapping.geometry;
import net.minecraft.resources.ResourceLocation;
import org.geysermc.packgenerator.mapping.animation.BedrockAnimationContext;
import org.geysermc.packgenerator.pack.geometry.BedrockGeometry;
public record BedrockGeometryContext(BedrockGeometry.GeometryDefinition geometry, ResourceLocation texture) {}
public record BedrockGeometryContext(BedrockGeometry geometry, BedrockAnimationContext animation, ResourceLocation texture) {}

View File

@@ -4,16 +4,21 @@ import net.minecraft.client.renderer.block.model.BlockElement;
import net.minecraft.client.renderer.block.model.BlockElementFace;
import net.minecraft.client.renderer.block.model.BlockElementRotation;
import net.minecraft.client.renderer.block.model.SimpleUnbakedGeometry;
import net.minecraft.client.resources.model.ResolvedModel;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import org.geysermc.packgenerator.mapping.animation.AnimationMapper;
import org.geysermc.packgenerator.pack.geometry.BedrockGeometry;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import java.util.Map;
public class GeometryMapper {
private static final Vector3fc CENTRE_OFFSET = new Vector3f(8.0F, 0.0F, 8.0F);
public static BedrockGeometry mapGeometry(String identifier, SimpleUnbakedGeometry geometry) {
public static BedrockGeometryContext mapGeometry(String identifier, ResolvedModel model, ResourceLocation texture) {
BedrockGeometry.Builder builder = BedrockGeometry.builder(identifier);
// Blockbench seems to always use these values
builder.withVisibleBoundsWidth(2.0F);
@@ -25,21 +30,30 @@ public class GeometryMapper {
builder.withTextureHeight(16);
BedrockGeometry.Bone.Builder bone = BedrockGeometry.bone(identifier);
// Blockbench always moves the pivot by 8 in the X and Z direction, and then moves the cubes back
bone.withPivot(new Vector3f(8.0F, 0.0F, -8.0F));
Vector3f min = new Vector3f(Float.MAX_VALUE);
Vector3f max = new Vector3f(Float.MIN_VALUE);
SimpleUnbakedGeometry geometry = (SimpleUnbakedGeometry) model.getTopGeometry();
for (BlockElement element : geometry.elements()) {
bone.withCube(mapBlockElement(element));
BedrockGeometry.Cube cube = mapBlockElement(element).build();
bone.withCube(cube);
min.min(cube.origin());
max.max(cube.origin().add(cube.size(), new Vector3f()));
}
// Calculate the pivot to be at the centre of the bone
// This is important for animations later, display animations rotate around the centre on Java
bone.withPivot(min.add(max.sub(min).div(2.0F)));
// Bind to the bone of the current item slot
bone.withBinding("q.item_slot_to_bone_name(context.item_slot)");
return builder.withBone(bone).build();
return new BedrockGeometryContext(builder.withBone(bone).build(), AnimationMapper.mapAnimation(identifier, identifier, model.getTopTransforms()), texture);
}
private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element) {
// Move the cube back by 8 in the X and Z direction
BedrockGeometry.Cube.Builder builder = BedrockGeometry.cube(element.from().sub(8.0F, 0.0F, 8.0F, new Vector3f()), element.to().sub(element.from(), new Vector3f()));
// The centre of the model is back by 8 in the X and Z direction on Java, so move the origin of the cube and the pivot like that
BedrockGeometry.Cube.Builder builder = BedrockGeometry.cube(element.from().sub(CENTRE_OFFSET, new Vector3f()), element.to().sub(element.from(), new Vector3f()));
for (Map.Entry<Direction, BlockElementFace> faceEntry : element.faces().entrySet()) {
// TODO texture key
@@ -68,7 +82,7 @@ public class GeometryMapper {
BlockElementRotation rotation = element.rotation();
if (rotation != null) {
builder.withPivot(rotation.origin());
builder.withPivot(rotation.origin().sub(CENTRE_OFFSET, new Vector3f()));
Vector3f bedrockRotation = switch (rotation.axis()) {
case X -> new Vector3f(rotation.angle(), 0.0F, 0.0F);