diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java index 798922a36..f7f49b234 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java @@ -12,7 +12,6 @@ public class SkinAnimation { protected int weight, left; protected List transitions; - @Deprecated public SkinAnimation() { this.transitions = new ArrayList<>(); } diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java index 81215599b..94b2504fb 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java @@ -4,6 +4,7 @@ import javafx.scene.*; import javafx.scene.image.Image; import javafx.scene.input.MouseEvent; import javafx.scene.input.ScrollEvent; +import javafx.scene.paint.Color; import javafx.scene.paint.Material; import javafx.scene.paint.PhongMaterial; import javafx.scene.shape.Shape3D; @@ -11,6 +12,8 @@ import javafx.scene.transform.Rotate; import javafx.scene.transform.Scale; import javafx.scene.transform.Translate; +import org.jetbrains.annotations.Nullable; + public class SkinCanvas extends Group { public static final Image ALEX = new Image(SkinCanvas.class.getResourceAsStream("/assets/img/alex.png")); @@ -22,7 +25,7 @@ public class SkinCanvas extends Group { public static final SkinCube STEVEN_LARM = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, false); public static final SkinCube STEVEN_RARM = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, false); - protected Image srcSkin, skin; + protected Image srcSkin, skin, srcCape, cape; protected boolean isSlim; protected double preW, preH; @@ -45,6 +48,8 @@ public class SkinCanvas extends Group { public final SkinCube llegInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 16F / 64F, 48F / 64F, 0F, false); public final SkinCube rlegInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 0F, 16F / 64F, 0F, false); + public final SkinCube capeCube = new SkinCube(10, 16, 1, 22F / 64F, 17F / 32F, 0F, 0F, 0F, false); + public final SkinGroup head = new SkinGroup( new Rotate(0, 0, headInside.getHeight() / 2, 0, Rotate.X_AXIS), new Rotate(0, Rotate.Y_AXIS), @@ -82,6 +87,13 @@ public class SkinCanvas extends Group { rlegOuter, rlegInside ); + public final SkinGroup capeGroup = new SkinGroup( + new Rotate(0, 0, -capeCube.getHeight() / 2, 0, Rotate.X_AXIS), + new Rotate(0, Rotate.Y_AXIS), + new Rotate(0, Rotate.Z_AXIS), + capeCube + ); + protected PerspectiveCamera camera = new PerspectiveCamera(true); protected Rotate xRotate = new Rotate(0, Rotate.X_AXIS); @@ -90,10 +102,10 @@ public class SkinCanvas extends Group { protected Translate translate = new Translate(0, 0, -80); protected Scale scale = new Scale(1, 1); - protected SkinAnimationPlayer animationplayer = new SkinAnimationPlayer(); + protected SkinAnimationPlayer animationPlayer = new SkinAnimationPlayer(); - public SkinAnimationPlayer getAnimationplayer() { - return animationplayer; + public SkinAnimationPlayer getAnimationPlayer() { + return animationPlayer; } public Image getSrcSkin() { @@ -104,20 +116,22 @@ public class SkinCanvas extends Group { return skin; } - public void updateSkin(Image skin, boolean isSlim) { + public void updateSkin(Image skin, boolean isSlim, final @Nullable Image cape) { if (SkinHelper.isNoRequest(skin) && SkinHelper.isSkin(skin)) { this.srcSkin = skin; this.skin = SkinHelper.x32Tox64(skin); + this.srcCape = cape; + this.cape = cape == null ? null : cape.getWidth() < 256 ? SkinHelper.enlarge(cape, 4, 8) : cape; int multiple = Math.max((int) (1024 / skin.getWidth()), 1); if (multiple > 1) - this.skin = SkinHelper.enlarge(this.skin, multiple); + this.skin = SkinHelper.enlarge(this.skin, multiple, multiple); if (this.isSlim != isSlim) - updateSkinModel(isSlim); + updateSkinModel(isSlim, cape != null); bindMaterial(root); } } - protected void updateSkinModel(boolean isSlim) { + protected void updateSkinModel(boolean isSlim, boolean hasCape) { this.isSlim = isSlim; FunctionHelper.alwaysB(SkinMultipleCubes::setWidth, isSlim ? 3 : 4, larmOuter, rarmOuter); FunctionHelper.alwaysB(SkinCube::setWidth, isSlim ? 3D : 4D, larmInside, rarmInside); @@ -134,6 +148,8 @@ public class SkinCanvas extends Group { larm.getZRotate().setPivotX(-larmInside.getWidth() / 2); rarm.getZRotate().setPivotX(+rarmInside.getWidth() / 2); + + capeGroup.setVisible(hasCape); } public SkinCanvas(double preW, double preH) { @@ -149,17 +165,17 @@ public class SkinCanvas extends Group { init(); } - protected Material createMaterial() { + protected Material createMaterial(final Image image) { PhongMaterial material = new PhongMaterial(); - material.setDiffuseMap(skin); + material.setDiffuseMap(image); return material; } protected void bindMaterial(Group group) { - Material material = createMaterial(); + Material material = createMaterial(skin); for (Node node : group.getChildren()) if (node instanceof Shape3D) - ((Shape3D) node).setMaterial(material); + ((Shape3D) node).setMaterial(node == capeCube ? createMaterial(cape) : material); else if (node instanceof SkinMultipleCubes) ((SkinMultipleCubes) node).updateSkin(skin); else if (node instanceof Group) @@ -178,6 +194,11 @@ public class SkinCanvas extends Group { lleg.setTranslateY(+(bodyInside.getHeight() + llegInside.getHeight()) / 2); rleg.setTranslateY(+(bodyInside.getHeight() + rlegInside.getHeight()) / 2); + capeGroup.setTranslateY(+(capeCube.getHeight() - bodyOuter.getHeight()) / 2); + capeGroup.setTranslateZ(-(bodyInside.getDepth() + bodyOuter.getDepth()) / 2); + + capeGroup.getTransforms().addAll(new Rotate(180, Rotate.Y_AXIS), new Rotate(10, Rotate.X_AXIS)); + root.getTransforms().addAll(xRotate); root.getChildren().addAll( @@ -186,15 +207,20 @@ public class SkinCanvas extends Group { larm, rarm, lleg, - rleg + rleg, + capeGroup ); - updateSkin(skin, false); + updateSkin(skin, false, null); return root; } protected SubScene createSubScene() { Group group = new Group(); + + AmbientLight light = new AmbientLight(Color.WHITE); + group.getChildren().add(light); + group.getChildren().add(createPlayerModel()); group.getTransforms().add(zRotate); diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java index 226ffca3e..efd000b12 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java @@ -1,9 +1,9 @@ package moe.mickey.minecraft.skin.fx; -import javafx.scene.image.Image; import javafx.scene.shape.Mesh; import javafx.scene.shape.MeshView; import javafx.scene.shape.TriangleMesh; + import org.apache.commons.lang3.ArrayUtils; public class SkinCube extends MeshView { @@ -20,61 +20,60 @@ public class SkinCube extends MeshView { width /= 2F; height /= 2F; depth /= 2F; - return new float[]{ - -width, -height, depth, // P0 - width, -height, depth, // P1 - -width, height, depth, // P2 - width, height, depth, // P3 - -width, -height, -depth, // P4 - width, -height, -depth, // P5 - -width, height, -depth, // P6 - width, height, -depth // P7 + -width, -height, +depth, // P0 + +width, -height, +depth, // P1 + -width, +height, +depth, // P2 + +width, +height, +depth, // P3 + -width, -height, -depth, // P4 + +width, -height, -depth, // P5 + -width, +height, -depth, // P6 + +width, +height, -depth // P7 }; } public static float[] createTexCoords(float width, float height, float depth, float scaleX, float scaleY, - float startX, float startY, boolean isSlim) { + float startX, float startY, boolean isSlim) { float x = (width + depth) * 2, y = height + depth, half_width = width / x * scaleX, half_depth = depth / x * scaleX, top_x = depth / x * scaleX + startX, top_y = startY, arm4 = isSlim ? half_depth : half_width, bottom_x = startX, middle_y = depth / y * scaleY + top_y, bottom_y = scaleY + top_y; return new float[]{ top_x, top_y, // T0 --- - top_x + half_width, top_y, // T1 | - top_x + half_width * 2, top_y, // T2 --- - bottom_x, middle_y, // T3 --- - bottom_x + half_depth, middle_y, // T4 | + top_x + half_width, top_y, // T1 | + top_x + half_width * 2, top_y, // T2 --- + bottom_x, middle_y, // T3 --- + bottom_x + half_depth, middle_y, // T4 | bottom_x + half_depth + half_width, middle_y, // T5 | - bottom_x + scaleX - arm4, middle_y, // T6 | - bottom_x + scaleX, middle_y, // T7 --- - bottom_x, bottom_y, // T8 --- - bottom_x + half_depth, bottom_y, // T9 | + bottom_x + scaleX - arm4, middle_y, // T6 | + bottom_x + scaleX, middle_y, // T7 --- + bottom_x, bottom_y, // T8 --- + bottom_x + half_depth, bottom_y, // T9 | bottom_x + half_depth + half_width, bottom_y, // T10 | - bottom_x + scaleX - arm4, bottom_y, // T11 | - bottom_x + scaleX, bottom_y // T12 --- + bottom_x + scaleX - arm4, bottom_y, // T11 | + bottom_x + scaleX, bottom_y // T12 --- }; } public static int[] createFaces() { - int[] faces = new int[]{ + int[] faces = { // TOP 5, 0, 4, 1, 0, 5, //P5,T0, P4,T1, P0,T5 5, 0, 0, 5, 1, 4, //P5,T0, P0,T5, P1,T4 // RIGHT - 0, 5, 4, 6, 6, 11, //P0,T4 ,P4,T3, P6,T8 - 0, 5, 6, 11, 2, 10, //P0,T4 ,P6,T8, P2,T9 + 0, 5, 4, 6, 6, 11, //P0,T4 ,P4,T3, P6,T8 + 0, 5, 6, 11, 2, 10, //P0,T4 ,P6,T8, P2,T9 // FRONT - 1, 4, 0, 5, 2, 10, //P1,T5, P0,T4, P2,T9 - 1, 4, 2, 10, 3, 9, //P1,T5, P2,T9, P3,T10 + 1, 4, 0, 5, 2, 10, //P1,T5, P0,T4, P2,T9 + 1, 4, 2, 10, 3, 9, //P1,T5, P2,T9, P3,T10 // LEFT 5, 3, 1, 4, 3, 9, //P5,T6, P1,T5, P3,T10 5, 3, 3, 9, 7, 8, //P5,T6, P3,T10,P7,T11 // BACK - 4, 6, 5, 7, 7, 12, //P4,T6, P5,T7, P7,T12 - 4, 6, 7, 12, 6, 11, //P4,T6, P7,T12,P6,T11 + 4, 6, 5, 7, 7, 12, //P4,T6, P5,T7, P7,T12 + 4, 6, 7, 12, 6, 11, //P4,T6, P7,T12,P6,T11 // BOTTOM 3, 5, 2, 6, 6, 2, //P3,T2, P2,T1, P6,T5 - 3, 5, 6, 2, 7, 1 //P3,T2, P6,T5, P7,T6 + 3, 5, 6, 2, 7, 1 //P3,T2, P6,T5, P7,T6 }; int[] copy = faces.clone(); @@ -91,7 +90,6 @@ public class SkinCube extends MeshView { private double width, height, depth; private boolean isSlim; - private Image skin; private Mesh model; public SkinCube(float width, float height, float depth, float scaleX, float scaleY, float startX, float startY, float enlarge, boolean isSlim) { diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java index 83f41e18c..5125f3e0a 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java @@ -10,12 +10,12 @@ public final class SkinHelper { private SkinHelper() { } - public static class PixelCopyer { + public static class PixelCopier { protected Image srcImage; protected WritableImage newImage; - public PixelCopyer(Image srcImage, WritableImage newImage) { + public PixelCopier(Image srcImage, WritableImage newImage) { this.srcImage = srcImage; this.newImage = newImage; } @@ -70,53 +70,53 @@ public final class SkinHelper { } public static Image x32Tox64(Image srcSkin) { - if (srcSkin.getHeight() == 64) + if (srcSkin.getHeight() == srcSkin.getWidth()) return srcSkin; WritableImage newSkin = new WritableImage((int) srcSkin.getWidth(), (int) srcSkin.getHeight() * 2); - PixelCopyer copyer = new PixelCopyer(srcSkin, newSkin); + PixelCopier copier = new PixelCopier(srcSkin, newSkin); // HEAD & HAT - copyer.copy(0 / 64F, 0 / 32F, 0 / 64F, 0 / 64F, 64 / 64F, 16 / 32F); + copier.copy(0 / 64F, 0 / 32F, 0 / 64F, 0 / 64F, 64 / 64F, 16 / 32F); // LEFT-LEG - x32Tox64(copyer, 0 / 64F, 16 / 32F, 16 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F); + x32Tox64(copier, 0 / 64F, 16 / 32F, 16 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F); // RIGHT-LEG - copyer.copy(0 / 64F, 16 / 32F, 0 / 64F, 16 / 64F, 16 / 64F, 16 / 32F); + copier.copy(0 / 64F, 16 / 32F, 0 / 64F, 16 / 64F, 16 / 64F, 16 / 32F); // BODY - copyer.copy(16 / 64F, 16 / 32F, 16 / 64F, 16 / 64F, 24 / 64F, 16 / 32F); + copier.copy(16 / 64F, 16 / 32F, 16 / 64F, 16 / 64F, 24 / 64F, 16 / 32F); // LEFT-ARM - x32Tox64(copyer, 40 / 64F, 16 / 32F, 32 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F); + x32Tox64(copier, 40 / 64F, 16 / 32F, 32 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F); // RIGHT-ARM - copyer.copy(40 / 64F, 16 / 32F, 40 / 64F, 16 / 64F, 16 / 64F, 16 / 32F); + copier.copy(40 / 64F, 16 / 32F, 40 / 64F, 16 / 64F, 16 / 64F, 16 / 32F); return newSkin; } - static void x32Tox64(PixelCopyer copyer, float srcX, float srcY, float toX, float toY, float width, float height, float depth) { + static void x32Tox64(PixelCopier copier, float srcX, float srcY, float toX, float toY, float width, float height, float depth) { // TOP - copyer.copy(srcX + depth, srcY, toX + depth, toY, width, depth * 2, true, false); + copier.copy(srcX + depth, srcY, toX + depth, toY, width, depth * 2, true, false); // BOTTOM - copyer.copy(srcX + depth + width, srcY, toX + depth + width, toY, width, depth * 2, true, false); + copier.copy(srcX + depth + width, srcY, toX + depth + width, toY, width, depth * 2, true, false); // INS - copyer.copy(srcX, srcY + depth * 2, toX + width + depth, toY + depth, depth, height, true, false); + copier.copy(srcX, srcY + depth * 2, toX + width + depth, toY + depth, depth, height, true, false); // OUTS - copyer.copy(srcX + width + depth, srcY + depth * 2, toX, toY + depth, depth, height, true, false); + copier.copy(srcX + width + depth, srcY + depth * 2, toX, toY + depth, depth, height, true, false); // FRONT - copyer.copy(srcX + depth, srcY + depth * 2, toX + depth, toY + depth, width, height, true, false); + copier.copy(srcX + depth, srcY + depth * 2, toX + depth, toY + depth, width, height, true, false); // BACK - copyer.copy(srcX + width + depth * 2, srcY + depth * 2, toX + width + depth * 2, toY + depth, width, height, true, false); + copier.copy(srcX + width + depth * 2, srcY + depth * 2, toX + width + depth * 2, toY + depth, width, height, true, false); } - public static Image enlarge(Image srcSkin, int multiple) { - WritableImage newSkin = new WritableImage((int) srcSkin.getWidth() * multiple, (int) srcSkin.getHeight() * multiple); + public static Image enlarge(Image srcSkin, int multipleX, int multipleY) { + WritableImage newSkin = new WritableImage((int) srcSkin.getWidth() * multipleX, (int) srcSkin.getHeight() * multipleY); PixelReader reader = srcSkin.getPixelReader(); PixelWriter writer = newSkin.getPixelWriter(); for (int x = 0, lenX = (int) srcSkin.getWidth(); x < lenX; x++) for (int y = 0, lenY = (int) srcSkin.getHeight(); y < lenY; y++) - for (int mx = 0; mx < multiple; mx++) - for (int my = 0; my < multiple; my++) { + for (int mx = 0; mx < multipleX; mx++) + for (int my = 0; my < multipleY; my++) { int argb = reader.getArgb(x, y); - writer.setArgb(x * multiple + mx, y * multiple + my, argb); + writer.setArgb(x * multipleX + mx, y * multipleY + my, argb); } return newSkin; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java index 54e0259d9..2e4049f8d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java @@ -76,7 +76,7 @@ public class OfflineAccountSkinPane extends StackPane { canvasPane.setPrefWidth(300); canvasPane.setPrefHeight(300); pane.setCenter(canvas); - canvas.getAnimationplayer().addSkinAnimation(new SkinAniWavingArms(100, 2000, 7.5, canvas), new SkinAniRunning(100, 100, 30, canvas)); + canvas.getAnimationPlayer().addSkinAnimation(new SkinAniWavingArms(100, 2000, 7.5, canvas), new SkinAniRunning(100, 100, 30, canvas)); canvas.enableRotation(.5); canvas.addEventHandler(DragEvent.DRAG_OVER, e -> { @@ -136,11 +136,14 @@ public class OfflineAccountSkinPane extends StackPane { LOG.log(Level.WARNING, "Failed to load skin", exception); Controllers.showToast(i18n("message.failed")); } else { - if (result == null || result.getSkin() == null) { - canvas.updateSkin(getDefaultTexture(), isDefaultSlim()); + if (result == null || result.getSkin() == null && result.getCape() == null) { + canvas.updateSkin(getDefaultTexture(), isDefaultSlim(), null); return; } - canvas.updateSkin(new Image(result.getSkin().getInputStream()), result.getModel() == TextureModel.ALEX); + canvas.updateSkin( + result.getSkin() != null ? new Image(result.getSkin().getInputStream()) : getDefaultTexture(), + result.getModel() == TextureModel.ALEX, + result.getCape() != null ? new Image(result.getCape().getInputStream()) : null); } }).start(); }, skinItem.selectedDataProperty(), cslApiField.textProperty(), skinSelector.valueProperty(), capeSelector.valueProperty());