From cbe9f7474eeda348849c8e1f917f6dc57cf50bee Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Wed, 22 Sep 2021 13:38:35 +0800 Subject: [PATCH] feat: skin viewer 3d --- .../minecraft/skin/fx/FunctionHelper.java | 60 +++++ .../minecraft/skin/fx/SkinAnimation.java | 58 +++++ .../skin/fx/SkinAnimationPlayer.java | 93 ++++++++ .../mickey/minecraft/skin/fx/SkinCanvas.java | 221 ++++++++++++++++++ .../minecraft/skin/fx/SkinCanvasSupport.java | 118 ++++++++++ .../mickey/minecraft/skin/fx/SkinCube.java | 141 +++++++++++ .../mickey/minecraft/skin/fx/SkinGroup.java | 45 ++++ .../mickey/minecraft/skin/fx/SkinHelper.java | 138 +++++++++++ .../minecraft/skin/fx/SkinMultipleCubes.java | 161 +++++++++++++ .../minecraft/skin/fx/SkinTransition.java | 42 ++++ .../skin/fx/animation/SkinAniRunning.java | 29 +++ .../skin/fx/animation/SkinAniWavingArms.java | 25 ++ .../hmcl/util/JavaRuntimeDownloadTask.java | 57 +++++ 13 files changed, 1188 insertions(+) create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/FunctionHelper.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimationPlayer.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvasSupport.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinGroup.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinMultipleCubes.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinTransition.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniRunning.java create mode 100644 HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniWavingArms.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/util/JavaRuntimeDownloadTask.java diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/FunctionHelper.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/FunctionHelper.java new file mode 100644 index 000000000..2ded91f13 --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/FunctionHelper.java @@ -0,0 +1,60 @@ +package moe.mickey.minecraft.skin.fx; + +import java.util.Arrays; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import javafx.event.Event; +import javafx.event.EventHandler; + +public interface FunctionHelper { + + public static void always(Consumer consumer, T... ts) { + Arrays.asList(ts).forEach(consumer); + } + + public static void alwaysA(BiConsumer consumer, A a, B... bs) { + Arrays.asList(bs).forEach(b -> consumer.accept(a, b)); + } + + public static void alwaysB(BiConsumer consumer, B b, A... as) { + Arrays.asList(as).forEach(a -> consumer.accept(a, b)); + } + + public static BiConsumer exchange(BiConsumer consumer) { + return (b, a) -> consumer.accept(a, b); + } + + public static Consumer link(Consumer... consumers) { + return t -> { + for (Consumer consumer : consumers) + consumer.accept(t); + }; + } + + public static EventHandler link(EventHandler... handlers) { + return t -> { + for (EventHandler handler : handlers) + handler.handle(t); + }; + } + + public static Consumer link1(Function function, Consumer consumer) { + return a -> consumer.accept(function.apply(a)); + } + + public static BiConsumer link2(Function function, BiConsumer consumer) { + return (a, c) -> consumer.accept(function.apply(a), c); + } + + public static Consumer link2(Supplier supplier, BiConsumer consumer) { + return b -> consumer.accept(supplier.get(), b); + } + + public static Supplier link1(Supplier supplier, Function function) { + return () -> function.apply(supplier.get()); + } + +} 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 new file mode 100644 index 000000000..d160feca9 --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java @@ -0,0 +1,58 @@ +package moe.mickey.minecraft.skin.fx; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; + +public class SkinAnimation { + + protected int weight, left; + protected List transitions; + + @Deprecated + public SkinAnimation() { + this.transitions = new LinkedList<>(); + } + + public SkinAnimation(int weight, SkinTransition... transitions) { + this.weight = weight; + this.transitions = Arrays.asList(transitions); + init(); + } + + protected void init() { + transitions.forEach(t -> { + EventHandler oldHandler = t.getOnFinished(); + EventHandler newHandler = e -> left--; + newHandler = oldHandler == null ? newHandler : FunctionHelper.link(oldHandler, newHandler); + t.setOnFinished(newHandler); + }); + } + + public int getWeight() { + return weight; + } + + public boolean isPlaying() { + return left > 0; + } + + public void play() { + transitions.forEach(SkinTransition::play); + left = transitions.size(); + } + + public void playFromStart() { + transitions.forEach(SkinTransition::playFromStart); + left = transitions.size(); + } + + public void stop() { + transitions.forEach(SkinTransition::stop); + left = 0; + } + +} diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimationPlayer.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimationPlayer.java new file mode 100644 index 000000000..aa37d16af --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimationPlayer.java @@ -0,0 +1,93 @@ +package moe.mickey.minecraft.skin.fx; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Random; + +import javafx.animation.AnimationTimer; + +public class SkinAnimationPlayer { + + protected final Random random = new Random(); + protected LinkedList animations = new LinkedList<>(); + protected SkinAnimation playing; + protected boolean running; + protected int weightedSum = 0; + protected long lastPlayTime = -1L, interval = 10_000_000_000L; + protected AnimationTimer animationTimer = new AnimationTimer() { + @Override + public void handle(long now) { + if (playing == null || !playing.isPlaying() && now - lastPlayTime > interval) { + int nextAni = random.nextInt(weightedSum); + SkinAnimation tmp = null; + for (SkinAnimation animation : animations) { + nextAni -= animation.getWeight(); + tmp = animation; + if (nextAni <= 0) + break; + } + playing = tmp; + if (playing == null && animations.size() > 0) + playing = animations.getLast(); + if (playing != null) { + playing.playFromStart(); + lastPlayTime = now; + } + } + } + }; + + public int getWeightedSum() { + return weightedSum; + } + + public void setInterval(long interval) { + this.interval = interval; + if (interval <1) + animationTimer.stop(); + else + start(); + } + + public long getInterval() { + return interval; + } + + public long getLastPlayTime() { + return lastPlayTime; + } + + public boolean isRunning() { + return running; + } + + public boolean isPlaying() { + return playing != null; + } + + public SkinAnimation getPlaying() { + return playing; + } + + public void addSkinAnimation(SkinAnimation... animations) { + this.animations.addAll(Arrays.asList(animations)); + this.weightedSum = this.animations.stream().mapToInt(SkinAnimation::getWeight).sum(); + start(); + } + + public void start() { + if (!running && weightedSum > 0 && interval > 0) { + animationTimer.start(); + running = true; + } + } + + public void stop() { + if (running) + animationTimer.stop(); + if (playing != null) + playing.stop(); + running = false; + } + +} 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 new file mode 100644 index 000000000..95530ee05 --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java @@ -0,0 +1,221 @@ +package moe.mickey.minecraft.skin.fx; + +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.PerspectiveCamera; +import javafx.scene.SceneAntialiasing; +import javafx.scene.SubScene; +import javafx.scene.image.Image; +import javafx.scene.paint.Color; +import javafx.scene.paint.Material; +import javafx.scene.paint.PhongMaterial; +import javafx.scene.shape.Shape3D; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Scale; +import javafx.scene.transform.Translate; + +public class SkinCanvas extends Group { + + public static final Image ALEX = new Image(SkinCanvas.class.getResourceAsStream("/alex.png")); + public static final Image STEVE = new Image(SkinCanvas.class.getResourceAsStream("/steve.png")); + public static final Image CHOCOLATE = new Image(SkinCanvas.class.getResourceAsStream("/chocolate.png")); + + public static final SkinCube ALEX_LARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, true); + public static final SkinCube ALEX_RARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, true); + + 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 boolean isSlim; + + protected double preW, preH; + protected boolean msaa; + + protected SubScene subScene; + protected Group root = new Group(); + + public final SkinMultipleCubes headOuter = new SkinMultipleCubes(8, 8, 8, 32F / 64F, 0F, 1.125, 0.2); + public final SkinMultipleCubes bodyOuter = new SkinMultipleCubes(8, 12, 4, 16F / 64F, 32F / 64F, 1, 0.2); + public final SkinMultipleCubes larmOuter = new SkinMultipleCubes(4, 12, 4, 48F / 64F, 48F / 64F, 1.0625, 0.2); + public final SkinMultipleCubes rarmOuter = new SkinMultipleCubes(4, 12, 4, 40F / 64F, 32F / 64F, 1.0625, 0.2); + public final SkinMultipleCubes llegOuter = new SkinMultipleCubes(4, 12, 4, 0F / 64F, 48F / 64F, 1.0625, 0.2); + public final SkinMultipleCubes rlegOuter = new SkinMultipleCubes(4, 12, 4, 0F / 64F, 32F / 64F, 1.0625, 0.2); + + public final SkinCube headInside = new SkinCube(8, 8, 8, 32F / 64F, 16F / 64F, 0F, 0F, 0F, false); + public final SkinCube bodyInside = new SkinCube(8, 12, 4, 24F / 64F, 16F / 64F, 16F / 64F, 16F / 64F, 0.03F, false); + public final SkinCube larmInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, false); + public final SkinCube rarmInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, false); + 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 SkinGroup head = new SkinGroup( + new Rotate(0, 0, headInside.getHeight() / 2, 0, Rotate.X_AXIS), + new Rotate(0, Rotate.Y_AXIS), + new Rotate(0, 0, headInside.getHeight() / 2, 0, Rotate.Z_AXIS), + headOuter, headInside + ); + public final SkinGroup body = new SkinGroup( + new Rotate(0, Rotate.X_AXIS), + new Rotate(0, Rotate.Y_AXIS), + new Rotate(0, Rotate.Z_AXIS), + bodyOuter, bodyInside + ); + public final SkinGroup larm = new SkinGroup( + new Rotate(0, 0, -larmInside.getHeight() / 2, 0, Rotate.X_AXIS), + new Rotate(0, Rotate.Y_AXIS), + new Rotate(0, +larmInside.getWidth() / 2, -larmInside.getHeight() / 2, 0, Rotate.Z_AXIS), + larmOuter, larmInside + ); + public final SkinGroup rarm = new SkinGroup( + new Rotate(0, 0, -rarmInside.getHeight() / 2, 0, Rotate.X_AXIS), + new Rotate(0, Rotate.Y_AXIS), + new Rotate(0, -rarmInside.getWidth() / 2, -rarmInside.getHeight() / 2, 0, Rotate.Z_AXIS), + rarmOuter, rarmInside + ); + public final SkinGroup lleg = new SkinGroup( + new Rotate(0, 0, -llegInside.getHeight() / 2, 0, Rotate.X_AXIS), + new Rotate(0, Rotate.Y_AXIS), + new Rotate(0, 0, -llegInside.getHeight() / 2, 0, Rotate.Z_AXIS), + llegOuter, llegInside + ); + public final SkinGroup rleg = new SkinGroup( + new Rotate(0, 0, -rlegInside.getHeight() / 2, 0, Rotate.X_AXIS), + new Rotate(0, Rotate.Y_AXIS), + new Rotate(0, 0, -rlegInside.getHeight() / 2, 0, Rotate.Z_AXIS), + rlegOuter, rlegInside + ); + + protected PerspectiveCamera camera = new PerspectiveCamera(true); + + protected Rotate xRotate = new Rotate(0, Rotate.X_AXIS); + protected Rotate yRotate = new Rotate(180, Rotate.Y_AXIS); + protected Rotate zRotate = new Rotate(0, Rotate.Z_AXIS); + protected Translate translate = new Translate(0, 0, -80); + protected Scale scale = new Scale(1, 1); + + protected SkinAnimationPlayer animationplayer = new SkinAnimationPlayer(); + + public SkinAnimationPlayer getAnimationplayer() { + return animationplayer; + } + + public Image getSrcSkin() { + return srcSkin; + } + + public Image getSkin() { + return skin; + } + + public void updateSkin(Image skin, boolean isSlim) { + if (SkinHelper.isNoRequest(skin) && SkinHelper.isSkin(skin)) { + this.srcSkin = skin; + this.skin = SkinHelper.x32Tox64(skin); + int multiple = Math.max((int) (1024 / skin.getWidth()), 1); + if (multiple > 1) + this.skin = SkinHelper.enlarge(this.skin, multiple); + if (this.isSlim != isSlim) + updateSkinModel(isSlim); + bindMaterial(root); + } + } + + protected void updateSkinModel(boolean isSlim) { + this.isSlim = isSlim; + FunctionHelper.alwaysB(SkinMultipleCubes::setWidth, isSlim ? 3 : 4, larmOuter, rarmOuter); + FunctionHelper.alwaysB(SkinCube::setWidth, isSlim ? 3D : 4D, larmInside, rarmInside); + + FunctionHelper.alwaysB(Node::setTranslateX, -(bodyInside.getWidth() + larmInside.getWidth()) / 2, larm); + FunctionHelper.alwaysB(Node::setTranslateX, +(bodyInside.getWidth() + rarmInside.getWidth()) / 2, rarm); + if (isSlim) { + larmInside.setModel(ALEX_LARM.getModel()); + rarmInside.setModel(ALEX_RARM.getModel()); + } else { + larmInside.setModel(STEVEN_LARM.getModel()); + rarmInside.setModel(STEVEN_RARM.getModel()); + } + + larm.getZRotate().setPivotX(-larmInside.getWidth() / 2); + rarm.getZRotate().setPivotX(+rarmInside.getWidth() / 2); + } + + public SkinCanvas(double preW, double preH) { + this(STEVE, preW, preH, true); + } + + public SkinCanvas(Image skin, double preW, double preH, boolean msaa) { + this.skin = skin; + this.preW = preW; + this.preH = preH; + this.msaa = msaa; + + init(); + } + + protected Material createMaterial() { + PhongMaterial material = new PhongMaterial(); + material.setDiffuseMap(skin); + return material; + } + + protected void bindMaterial(Group group) { + Material material = createMaterial(); + for (Node node : group.getChildren()) + if (node instanceof Shape3D) + ((Shape3D) node).setMaterial(material); + else if (node instanceof SkinMultipleCubes) + ((SkinMultipleCubes) node).updateSkin(skin); + else if (node instanceof Group) + bindMaterial((Group) node); + } + + + + protected Group createPlayerModel() { + head.setTranslateY(-(bodyInside.getHeight() + headInside.getHeight()) / 2); + + larm.setTranslateX(-(bodyInside.getWidth() + larmInside.getWidth()) / 2); + rarm.setTranslateX(+(bodyInside.getWidth() + rarmInside.getWidth()) / 2); + + lleg.setTranslateX(-(bodyInside.getWidth() - llegInside.getWidth()) / 2); + rleg.setTranslateX(+(bodyInside.getWidth() - rlegInside.getWidth()) / 2); + + lleg.setTranslateY(+(bodyInside.getHeight() + llegInside.getHeight()) / 2); + rleg.setTranslateY(+(bodyInside.getHeight() + rlegInside.getHeight()) / 2); + + root.getTransforms().addAll(xRotate); + + root.getChildren().addAll( + head, + body, + larm, + rarm, + lleg, + rleg + ); + updateSkin(skin, false); + + return root; + } + + protected SubScene createSubScene() { + Group group = new Group(); + group.getChildren().add(createPlayerModel()); + group.getTransforms().add(zRotate); + + camera.getTransforms().addAll(yRotate, translate, scale); + + subScene = new SubScene(group, preW, preH, true, + msaa ? SceneAntialiasing.BALANCED : SceneAntialiasing.DISABLED); + subScene.setFill(Color.ALICEBLUE); + subScene.setCamera(camera); + + return subScene; + } + + protected void init() { + getChildren().add(createSubScene()); + } + +} diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvasSupport.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvasSupport.java new file mode 100644 index 000000000..eac5896dc --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvasSupport.java @@ -0,0 +1,118 @@ +package moe.mickey.minecraft.skin.fx; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.function.Consumer; + +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.input.DragEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.ScrollEvent; +import javafx.scene.input.TransferMode; +import javafx.stage.Stage; +import moe.mickey.minecraft.skin.fx.test.Test; + +public abstract class SkinCanvasSupport implements Consumer { + + public static final class Mouse extends SkinCanvasSupport { + + private double lastX, lastY, sensitivity; + + public void setSensitivity(double sensitivity) { + this.sensitivity = sensitivity; + } + + public double getSensitivity() { + return sensitivity; + } + + public Mouse(double sensitivity) { + this.sensitivity = sensitivity; + } + + @Override + public void accept(SkinCanvas t) { + t.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> { + lastX = -1; + lastY = -1; + }); + t.addEventHandler(MouseEvent.MOUSE_DRAGGED, e -> { + if (!(lastX == -1 || lastY == -1)) { + if (e.isAltDown() || e.isControlDown() || e.isShiftDown()) { + if (e.isShiftDown()) + t.zRotate.setAngle(t.zRotate.getAngle() - (e.getSceneY() - lastY) * sensitivity); + if (e.isAltDown()) + t.yRotate.setAngle(t.yRotate.getAngle() + (e.getSceneX() - lastX) * sensitivity); + if (e.isControlDown()) + t.xRotate.setAngle(t.xRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity); + } else { + double yaw = t.yRotate.getAngle() + (e.getSceneX() - lastX) * sensitivity; + yaw %= 360; + if (yaw < 0) + yaw += 360; + + int flagX = yaw < 90 || yaw > 270 ? 1 : -1; + int flagZ = yaw < 180 ? -1 : 1; + double kx = Math.abs(90 - yaw % 180) / 90 * flagX, kz = Math.abs(90 - (yaw + 90) % 180) / 90 * flagZ; + + t.xRotate.setAngle(t.xRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity * kx); + t.yRotate.setAngle(yaw); + t.zRotate.setAngle(t.zRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity * kz); + } + } + lastX = e.getSceneX(); + lastY = e.getSceneY(); + }); + t.addEventHandler(ScrollEvent.SCROLL, e -> { + double delta = (e.getDeltaY() > 0 ? 1 : e.getDeltaY() == 0 ? 0 : -1) / 10D * sensitivity; + t.scale.setX(Math.min(Math.max(t.scale.getX() - delta, 0.1), 10)); + t.scale.setY(Math.min(Math.max(t.scale.getY() - delta, 0.1), 10)); + }); + } + + } + + public static class Drag extends SkinCanvasSupport { + + private String title; + + public Drag(String title) { + this.title = title; + } + + @Override + public void accept(SkinCanvas t) { + t.addEventHandler(DragEvent.DRAG_OVER, e -> { + if (e.getDragboard().hasFiles()) { + File file = e.getDragboard().getFiles().get(0); + if (file.getAbsolutePath().endsWith(".png")) + e.acceptTransferModes(TransferMode.COPY); + } + }); + t.addEventHandler(DragEvent.DRAG_DROPPED, e -> { + if (e.isAccepted()) { + File skin = e.getDragboard().getFiles().get(0); + Platform.runLater(() -> { + try { + FileInputStream input = new FileInputStream(skin); + Stage stage = new Stage(); + stage.setTitle(title); + SkinCanvas canvas = Test.createSkinCanvas(); + canvas.updateSkin(new Image(input), skin.getName().contains("[alex]")); + Scene scene = new Scene(canvas); + stage.setScene(scene); + stage.show(); + } catch (FileNotFoundException ex) { + ex.printStackTrace(); + } + }); + } + }); + } + + } + +} 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 new file mode 100644 index 000000000..107b27464 --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java @@ -0,0 +1,141 @@ +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; + +public class SkinCube extends MeshView { + + public static class Model extends TriangleMesh { + + public Model(float width, float height, float depth, float scaleX, float scaleY, float startX, float startY, boolean isSlim) { + getPoints().addAll(createPoints(width, height, depth)); + getTexCoords().addAll(createTexCoords(width, height, depth, scaleX, scaleY, startX, startY, isSlim)); + getFaces().addAll(createFaces()); + } + + public static float[] createPoints(float width, float height, float depth) { + 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 + }; + } + + public static float[] createTexCoords(float width, float height, float depth, float scaleX, float scaleY, + 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 | + 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 + half_depth + half_width, bottom_y, // T10 | + bottom_x + scaleX - arm4, bottom_y, // T11 | + bottom_x + scaleX, bottom_y // T12 --- + }; + } + + public static int[] createFaces() { + int faces[] = new int[]{ + // 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 + // 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 + // 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 + // 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 + }; + + int copy[] = ArrayUtils.clone(faces); + ArrayUtils.reverse(copy); + for (int i = 0; i < copy.length; i += 2) { + int tmp = copy[i]; + copy[i] = copy[i + 1]; + copy[i + 1] = tmp; + } + return ArrayUtils.addAll(faces, copy); + } + + } + + 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) { + this.width = width; + this.height = height; + this.depth = depth; + this.isSlim = isSlim; + setMesh(model = new Model(width + enlarge, height + enlarge, depth + enlarge, scaleX, scaleY, startX, startY, isSlim)); + } + + public void setWidth(double width) { + this.width = width; + } + + public double getWidth() { + return width; + } + + public void setHeight(double height) { + this.height = height; + } + + public double getHeight() { + return height; + } + + public void setDepth(double depth) { + this.depth = depth; + } + + public double getDepth() { + return depth; + } + + public boolean isSlim() { + return isSlim; + } + + public Mesh getModel() { + return model; + } + + public void setModel(Mesh model) { + this.model = model; + setMesh(model); + } + +} diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinGroup.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinGroup.java new file mode 100644 index 000000000..c66712bb8 --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinGroup.java @@ -0,0 +1,45 @@ +package moe.mickey.minecraft.skin.fx; + +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.transform.Rotate; + +public class SkinGroup extends Group { + + protected Rotate xRotate, yRotate, zRotate; + + public SkinGroup(Rotate xRotate, Rotate yRotate, Rotate zRotate, Node... nodes) { + this.xRotate = xRotate; + this.yRotate = yRotate; + this.zRotate = zRotate; + Group group = new Group(); + group.getChildren().addAll(nodes); + getChildren().add(addRotate(group, xRotate, yRotate, zRotate)); + } + + protected Group addRotate(Group group, Rotate... rotates) { + for (Rotate rotate : rotates) + group = addRotate(group, rotate); + return group; + } + + protected Group addRotate(Group group, Rotate rotate) { + Group newGroup = new Group(); + group.getTransforms().add(rotate); + newGroup.getChildren().add(group); + return newGroup; + } + + public Rotate getXRotate() { + return xRotate; + } + + public Rotate getYRotate() { + return yRotate; + } + + public Rotate getZRotate() { + return zRotate; + } + +} 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 new file mode 100644 index 000000000..4f02f45f1 --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java @@ -0,0 +1,138 @@ +package moe.mickey.minecraft.skin.fx; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.Image; +import javafx.scene.image.PixelReader; +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; + +public interface SkinHelper { + + public static class PixelCopyer { + + protected Image srcImage; + protected WritableImage newImage; + + public PixelCopyer(Image srcImage, WritableImage newImage) { + this.srcImage = srcImage; + this.newImage = newImage; + } + + public void copy(int srcX, int srcY, int width, int height) { + copy(srcX, srcY, srcX, srcY, width, height); + } + + public void copy(int srcX, int srcY, int toX, int toY, int width, int height) { + copy(srcX, srcY, toX, toY, width, height, false, false); + } + + public void copy(int srcX, int srcY, int toX, int toY, int width, int height, boolean reversalX, boolean reversalY) { + PixelReader reader = srcImage.getPixelReader(); + PixelWriter writer = newImage.getPixelWriter(); + for (int x = 0; x < width; x++) + for (int y = 0; y < height; y++) + writer.setArgb(toX + x, toY + y, + reader.getArgb(srcX + (reversalX ? width - x - 1 : x), srcY + (reversalY ? height - y - 1 : y))); + } + + public void copy(float srcX, float srcY, float toX, float toY, float width, float height) { + copy(srcX, srcY, toX, toY, width, height, false, false); + } + + public void copy(float srcX, float srcY, float toX, float toY, float width, float height, boolean reversalX, boolean reversalY) { + PixelReader reader = srcImage.getPixelReader(); + PixelWriter writer = newImage.getPixelWriter(); + int srcScaleX = (int) srcImage.getWidth(); + int srcScaleY = (int) srcImage.getHeight(); + int newScaleX = (int) newImage.getWidth(); + int newScaleY = (int) newImage.getHeight(); + int srcWidth = (int) (width * srcScaleX); + int srcHeight = (int) (height * srcScaleY); + + for (int x = 0; x < srcWidth; x++) + for (int y = 0; y < srcHeight; y++) + writer.setArgb((int) (toX * newScaleX + x), (int) (toY * newScaleY + y), + reader.getArgb((int) (srcX * srcScaleX + (reversalX ? srcWidth - x - 1 : x)), + (int) (srcY * srcScaleY + (reversalY ? srcHeight - y - 1 : y)))); + } + + } + + public static boolean isNoRequest(Image image) { + return image.getRequestedWidth() == 0 && image.getRequestedHeight() == 0; + } + + public static boolean isSkin(Image image) { + return image.getWidth() % 64 == 0 && image.getWidth() / 64 > 0 && + (image.getHeight() == image.getWidth() / 2 || image.getHeight() == image.getWidth()); + } + + public static Image x32Tox64(Image srcSkin) { + if (srcSkin.getHeight() == 64) + return srcSkin; + + WritableImage newSkin = new WritableImage((int) srcSkin.getWidth(), (int) srcSkin.getHeight() * 2); + PixelCopyer copyer = new PixelCopyer(srcSkin, newSkin); + // HEAD & HAT + copyer.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); + // RIGHT-LEG + copyer.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); + // LEFT-ARM + x32Tox64(copyer, 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); + + return newSkin; + } + + static void x32Tox64(PixelCopyer copyer, 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); + // BOTTOM + copyer.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); + // OUTS + copyer.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); + // BACK + copyer.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); + 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++) { + int argb = reader.getArgb(x, y); + writer.setArgb(x * multiple + mx, y * multiple + my, argb); + } + + return newSkin; + } + + public static void saveToFile(Image image, File output) { + BufferedImage buffer = SwingFXUtils.fromFXImage(image, null); + try { + ImageIO.write(buffer, "png", output); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinMultipleCubes.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinMultipleCubes.java new file mode 100644 index 000000000..fa1e7086d --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinMultipleCubes.java @@ -0,0 +1,161 @@ +package moe.mickey.minecraft.skin.fx; + +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.image.Image; +import javafx.scene.image.PixelReader; +import javafx.scene.paint.Color; +import javafx.scene.paint.Material; +import javafx.scene.paint.PhongMaterial; +import javafx.scene.shape.Box; + +public class SkinMultipleCubes extends Group { + + public static class Face extends Group { + + public Face(Image image, int startX, int startY, int width, int height, int interval, boolean reverseX, boolean reverseY, + Supplier supplier, BiConsumer consumer) { + PixelReader reader = image.getPixelReader(); + for (int x = 0; x < width; x++) + for (int y = 0; y < height; y++) { + int argb; + if ((argb = reader.getArgb(startX + (reverseX ? width - x - 1 : x) * interval, + startY + (reverseY ? height - y - 1 : y) * interval)) != 0) { + Box pixel = supplier.get(); + consumer.accept(pixel, new Point2D(x, y)); + pixel.setMaterial(createMaterial(Color.rgb( + (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, (argb >> 0) & 0xFF))); + getChildren().add(pixel); + } + } + } + + protected Material createMaterial(Color color) { + return new PhongMaterial(color); + } + + } + + protected int width, height, depth; + protected float startX, startY; + protected double length, thick; + + public SkinMultipleCubes(int width, int height, int depth, float startX, float startY, double length, double thick) { + this.width = width; + this.height = height; + this.depth = depth; + this.startX = startX; + this.startY = startY; + this.length = length; + this.thick = thick; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getWidth() { + return width; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getHeight() { + return height; + } + + public void setDepth(int depth) { + this.depth = depth; + } + + public int getDepth() { + return depth; + } + + public void setStartX(float startX) { + this.startX = startX; + } + + public float getStartX() { + return startX; + } + + public void setStartY(float startY) { + this.startY = startY; + } + + public float getStartY() { + return startY; + } + + public void setLength(double length) { + this.length = length; + } + + public double getLength() { + return length; + } + + public void setThick(double thick) { + this.thick = thick; + } + + public double getThick() { + return thick; + } + + public void updateSkin(Image skin) { + getChildren().clear(); + int start_x = (int) (startX * skin.getWidth()), start_y = (int) (startY * skin.getHeight()), + interval = (int) Math.max(skin.getWidth() / 64, 1), + width_interval = width * interval, height_interval = height * interval, depth_interval = depth * interval; + // FRONT + getChildren().add(new Face(skin, start_x + depth_interval, start_y + depth_interval, width, height, interval, false, false, + () -> new Box(length, length, thick), (b, p) -> { + b.setTranslateX(((width - 1) / 2.0 - p.getX()) * b.getWidth()); + b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight()); + b.setTranslateZ((depth * length + thick) / 2.0); + })); + // BACK + getChildren().add(new Face(skin, start_x + width_interval + depth_interval * 2, start_y + depth_interval, width, height, interval, true, false, + () -> new Box(length, length, thick), (b, p) -> { + b.setTranslateX(((width - 1) / 2.0 - p.getX()) * b.getWidth()); + b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight()); + b.setTranslateZ(-(depth * length + thick) / 2.0); + })); + // LEFT + getChildren().add(new Face(skin, start_x + width_interval + depth_interval, start_y + depth_interval, depth, height, interval, false, false, + () -> new Box(thick, length, length), (b, p) -> { + b.setTranslateX((width * length + thick) / 2.0); + b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight()); + b.setTranslateZ(((depth - 1) / 2.0 - p.getX()) * b.getDepth()); + })); + // RIGHT + getChildren().add(new Face(skin, start_x, start_y + depth_interval, depth, height, interval, true, false, + () -> new Box(thick, length, length), (b, p) -> { + b.setTranslateX(-(width * length + thick) / 2.0); + b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight()); + b.setTranslateZ(((depth - 1) / 2.0 - p.getX()) * b.getDepth()); + })); + // TOP + getChildren().add(new Face(skin, start_x + depth_interval, start_y, width, depth, interval, false, false, + () -> new Box(length, thick, length), (b, p) -> { + b.setTranslateX(((width - 1) / 2.0 - p.getX()) * b.getWidth()); + b.setTranslateY(-(height * length + thick) / 2.0); + b.setTranslateZ(-((depth - 1) / 2.0 - p.getY()) * b.getDepth()); + })); + // BOTTOM + getChildren().add(new Face(skin, start_x + width_interval + depth_interval, start_y, width, depth, interval, false, false, + () -> new Box(length, thick, length), (b, p) -> { + b.setTranslateX(((width - 1) / 2.0 - p.getX()) * b.getWidth()); + b.setTranslateY((height * length + thick) / 2.0); + b.setTranslateZ(-((depth - 1) / 2.0 - p.getY()) * b.getDepth()); + })); + } + +} diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinTransition.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinTransition.java new file mode 100644 index 000000000..ad132067f --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinTransition.java @@ -0,0 +1,42 @@ +package moe.mickey.minecraft.skin.fx; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +import javafx.animation.Transition; +import javafx.beans.value.WritableValue; +import javafx.util.Duration; + +public class SkinTransition extends Transition { + + protected Function expression; + protected List> observables; + protected boolean fix; + protected int count; + + public int getCount() { + return count; + } + + public SkinTransition(Duration duration, Function expression, WritableValue... observables) { + setCycleDuration(duration); + this.expression = expression; + this.observables = Arrays.asList(observables); + } + + @Override + protected void interpolate(double frac) { + if (frac == 0 || frac == 1) + count++; + double val = expression.apply(frac); + observables.forEach(w -> w.setValue(val)); + } + + @Override + public void play() { + count = 0; + super.play(); + } + +} diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniRunning.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniRunning.java new file mode 100644 index 000000000..4cbe6e5e8 --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniRunning.java @@ -0,0 +1,29 @@ +package moe.mickey.minecraft.skin.fx.animation; + +import javafx.util.Duration; +import moe.mickey.minecraft.skin.fx.FunctionHelper; +import moe.mickey.minecraft.skin.fx.SkinAnimation; +import moe.mickey.minecraft.skin.fx.SkinCanvas; +import moe.mickey.minecraft.skin.fx.SkinTransition; + +public final class SkinAniRunning extends SkinAnimation { + + private SkinTransition larmTransition, rarmTransition; + + public SkinAniRunning(int weight, int time, double angle, SkinCanvas canvas) { + larmTransition = new SkinTransition(Duration.millis(time), + v -> v * (larmTransition.getCount() % 4 < 2 ? 1 : -1) * angle, + canvas.larm.getXRotate().angleProperty(), canvas.rleg.getXRotate().angleProperty()); + + rarmTransition = new SkinTransition(Duration.millis(time), + v -> v * (rarmTransition.getCount() % 4 < 2 ? 1 : -1) * -angle, + canvas.rarm.getXRotate().angleProperty(), canvas.lleg.getXRotate().angleProperty()); + + FunctionHelper.alwaysB(SkinTransition::setAutoReverse, true, larmTransition, rarmTransition); + FunctionHelper.alwaysB(SkinTransition::setCycleCount, 16, larmTransition, rarmTransition); + FunctionHelper.always(transitions::add, larmTransition, rarmTransition); + this.weight = weight; + init(); + } + +} diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniWavingArms.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniWavingArms.java new file mode 100644 index 000000000..84544ba4d --- /dev/null +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniWavingArms.java @@ -0,0 +1,25 @@ +package moe.mickey.minecraft.skin.fx.animation; + +import javafx.util.Duration; +import moe.mickey.minecraft.skin.fx.FunctionHelper; +import moe.mickey.minecraft.skin.fx.SkinAnimation; +import moe.mickey.minecraft.skin.fx.SkinCanvas; +import moe.mickey.minecraft.skin.fx.SkinTransition; + +public final class SkinAniWavingArms extends SkinAnimation { + + public SkinAniWavingArms(int weight, int time, double angle, SkinCanvas canvas) { + SkinTransition larmTransition = new SkinTransition(Duration.millis(time), v -> v * angle, + canvas.larm.getZRotate().angleProperty()); + + SkinTransition rarmTransition = new SkinTransition(Duration.millis(time), v -> v * -angle, + canvas.rarm.getZRotate().angleProperty()); + + FunctionHelper.alwaysB(SkinTransition::setAutoReverse, true, larmTransition, rarmTransition); + FunctionHelper.alwaysB(SkinTransition::setCycleCount, 2, larmTransition, rarmTransition); + FunctionHelper.always(transitions::add, larmTransition, rarmTransition); + this.weight = weight; + init(); + } + +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaRuntimeDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaRuntimeDownloadTask.java new file mode 100644 index 000000000..b6731eb24 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaRuntimeDownloadTask.java @@ -0,0 +1,57 @@ +package org.jackhuang.hmcl.util; + +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.task.Task; + +import java.util.Collection; + +public class JavaRuntimeDownloadTask extends Task { + + @Override + public void execute() { + +// HttpRequest.GET("https://hmcl.huangyuhui.net/api/java", +// pair("os", OperatingSystem.CURRENT_OS.getCheckedName())); +// .getJson(); + + + } + + @Override + public Collection> getDependencies() { + return super.getDependencies(); + } + + public static class JavaDownload { + @SerializedName("version") + private final String version; + + @SerializedName("distro") + private final String distro; + + @SerializedName("url") + private final String url; + + public JavaDownload() { + this("", "", ""); + } + + public JavaDownload(String version, String distro, String url) { + this.version = version; + this.distro = distro; + this.url = url; + } + + public String getVersion() { + return version; + } + + public String getDistro() { + return distro; + } + + public String getUrl() { + return url; + } + } +}