All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.llbit.chunky.ui.render.Poser Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
/*
 * Copyright (c) 2016 Jesper Öqvist 
 *
 * This file is part of Chunky.
 *
 * Chunky is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Chunky is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with Chunky.  If not, see .
 */
package se.llbit.chunky.ui.render;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.TextField;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.WritableImage;
import javafx.scene.image.WritablePixelFormat;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.apache.commons.math3.util.FastMath;
import se.llbit.chunky.renderer.scene.Camera;
import se.llbit.chunky.renderer.scene.PlayerModel;
import se.llbit.chunky.ui.DoubleAdjuster;
import se.llbit.math.BVH;
import se.llbit.math.ColorUtil;
import se.llbit.math.Matrix3;
import se.llbit.math.QuickMath;
import se.llbit.math.Ray;
import se.llbit.math.Vector3;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.IntBuffer;
import java.util.Collections;
import java.util.LinkedList;
import java.util.ResourceBundle;

/**
 * A tool for posing entities.
 */
public class Poser extends Stage implements Initializable {
  private static final WritablePixelFormat PIXEL_FORMAT =
      PixelFormat.getIntArgbInstance();
  private final EntitiesTab.PlayerData player;
  private BVH bvh = new BVH(Collections.emptyList());
  private int[] pixels;
  private int width = 300;
  private int height = 300;
  private Matrix3 transform = new Matrix3();
  private Vector3 camPos = new Vector3();
  private WritableImage image;

  @FXML private Canvas preview;
  @FXML private ChoiceBox playerModel;
  @FXML private TextField skin;
  @FXML private Button selectSkin;
  @FXML private DoubleAdjuster direction;
  @FXML private DoubleAdjuster headYaw;
  @FXML private DoubleAdjuster headPitch;
  @FXML private DoubleAdjuster leftArmPose;
  @FXML private DoubleAdjuster rightArmPose;
  @FXML private DoubleAdjuster leftLegPose;
  @FXML private DoubleAdjuster rightLegPose;
  private double lastX;
  private double lastY;

  public Poser(EntitiesTab.PlayerData data) throws IOException {
    this.player = data;
    FXMLLoader loader = new FXMLLoader(getClass().getResource("Poser.fxml"));
    loader.setController(this);
    Parent root = loader.load();
    setScene(new javafx.scene.Scene(root));
    setTitle("Pose Preview");
  }

  @Override public void initialize(URL location, ResourceBundle resources) {
    selectSkin.setOnAction(e -> {
      FileChooser fileChooser = new FileChooser();
      fileChooser.setTitle("Load Skin");
      fileChooser
          .setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Minecraft skin", "*.png"));
      File skinFile = fileChooser.showOpenDialog(getScene().getWindow());
      if (skinFile != null) {
        player.entity.setTexture(skinFile.getAbsolutePath());
        skin.setText(skinFile.getAbsolutePath());
        redraw();
      }
    });
    preview.setOnMousePressed(e -> {
      lastX = e.getX();
      lastY = e.getY();
    });
    preview.setOnMouseDragged(e -> {
      double dx = e.getX() - lastX;
      double dy = e.getY() - lastY;
      lastX = e.getX();
      lastY = e.getY();
      direction.setAndUpdate(direction.get() + dx / 20);
      headPitch.setAndUpdate(headPitch.get() - dy / 60);
    });
    skin.setText(player.entity.skin);
    pixels = new int[width * height];
    transform.setIdentity();
    image = new WritableImage(width, height);
    playerModel.getItems().addAll(PlayerModel.values());
    playerModel.getSelectionModel().select(player.entity.model);
    playerModel.getSelectionModel().selectedItemProperty()
        .addListener((observable, oldValue, newValue) -> {
          player.entity.model = newValue;
          redraw();
        });
    direction.setName("Direction");
    direction.setRange(-Math.PI, Math.PI);
    direction.onValueChange(value -> {
      player.entity.yaw = value;
      redraw();
    });
    headYaw.setName("Head yaw");
    headYaw.setRange(-QuickMath.HALF_PI, QuickMath.HALF_PI);
    headYaw.onValueChange(value -> {
      player.entity.headYaw = value;
      redraw();
    });
    headPitch.setName("Head pitch");
    headPitch.setRange(-QuickMath.HALF_PI, QuickMath.HALF_PI);
    headPitch.onValueChange(value -> {
      player.entity.pitch = value;
      redraw();
    });
    leftArmPose.setName("Left arm pose");
    leftArmPose.setRange(-Math.PI, Math.PI);
    leftArmPose.onValueChange(value -> {
      player.entity.leftArmPose = value;
      redraw();
    });
    rightArmPose.setName("Right arm pose");
    rightArmPose.setRange(-Math.PI, Math.PI);
    rightArmPose.onValueChange(value -> {
      player.entity.rightArmPose = value;
      redraw();
    });
    leftLegPose.setName("Left leg pose");
    leftLegPose.setRange(-QuickMath.HALF_PI, QuickMath.HALF_PI);
    leftLegPose.onValueChange(value -> {
      player.entity.leftLegPose = value;
      redraw();
    });
    rightLegPose.setName("Right leg pose");
    rightLegPose.setRange(-QuickMath.HALF_PI, QuickMath.HALF_PI);
    rightLegPose.onValueChange(value -> {
      player.entity.rightLegPose = value;
      redraw();
    });
    redraw();
  }

  private void buildBvh() {
    Vector3 offset = new Vector3(); // Offset to place player in focus.
    offset.sub(player.entity.position);
    bvh = new BVH(new LinkedList<>(player.entity.primitives(offset)));
  }

  private void redraw() {
    buildBvh();
    GraphicsContext gc = preview.getGraphicsContext2D();
    Ray ray = new Ray();
    double aspect = width / (double) height;
    double fovTan = Camera.clampedFovTan(70);
    camPos.set(0, 1, -2);
    for (int y = 0; y < height; ++y) {
      double rayy = fovTan * (.5 - ((double) y) / height);
      for (int x = 0; x < width; ++x) {
        double rayx = fovTan * aspect * (.5 - ((double) x) / width);
        ray.setDefault();
        ray.t = Double.MAX_VALUE;
        ray.d.set(rayx, rayy, 1);
        ray.d.normalize();

        ray.o.set(camPos);
        while (true) {
          if (bvh.closestIntersection(ray)) {
            if (ray.color.w > 0.9) {
              break;
            }
            ray.o.scaleAdd(ray.t, ray.d);
          } else {
            if (x % 20 == 0 || y % 20 == 0) {
              ray.color.set(0.7, 0.7, 0.7, 1);
            } else {
              ray.color.set(1, 1, 1, 1);
            }
            break;
          }
        }

        ray.color.x = QuickMath.min(1, FastMath.sqrt(ray.color.x));
        ray.color.y = QuickMath.min(1, FastMath.sqrt(ray.color.y));
        ray.color.z = QuickMath.min(1, FastMath.sqrt(ray.color.z));
        pixels[y * width + x] = ColorUtil.getRGB(ray.color);
      }
    }
    image.getPixelWriter().setPixels(0, 0, width, height, PIXEL_FORMAT, pixels, 0, width);
    gc.drawImage(image, 0, 0);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy