---
category: swing
folder: PerspectiveCube
title: JPanelに正六面体ワイヤーフレームを投影変換して描画し、マウスドラッグして空間内で回転する
tags: [JPanel, Path2D, MouseListener, MouseMotionListener, Animation]
author: aterai
pubdate: 2023-01-02T03:10:52+09:00
description: JPanelに正六面体ワイヤーフレームを投影変換で描画し、マウスドラッグして各頂点を変換して空間内で回転します。
image: https://drive.google.com/uc?id=1akdvf9FZNwNey03l7ZI-DW9ZTtgAvtdY
hreflang:
    href: https://java-swing-tips.blogspot.com/2023/02/draw-projection-transformed-regular.html
    lang: en
---
* 概要 [#summary]
`JPanel`に正六面体ワイヤーフレームを投影変換で描画し、マウスドラッグして各頂点を変換して空間内で回転します。

#download(https://drive.google.com/uc?id=1akdvf9FZNwNey03l7ZI-DW9ZTtgAvtdY)

* サンプルコード [#sourcecode]
#code(link){{
class Vertex {
  private double dx;
  private double dy;
  private double dz;
  public double vx;
  public double vy;

  protected Vertex(double dx, double dy, double dz) {
    this.dx = dx;
    this.dy = dy;
    this.dz = dz;
    projectionTransformation();
  }

  private void projectionTransformation() {
    double screenDistance = 500d;
    double depth = 1000d;
    double gz = dz + depth;
    this.vx = screenDistance * dx / gz;
    this.vy = screenDistance * dy / gz;
  }

  public void rotateTransformation(double kx, double ky, double kz) {
    double x0 = dx * Math.cos(ky) - dz * Math.sin(ky);
    double y0 = dy;
    double z0 = dx * Math.sin(ky) + dz * Math.cos(ky);
    double y1 = y0 * Math.cos(kx) - z0 * Math.sin(kx);
    double z1 = y0 * Math.sin(kx) + z0 * Math.cos(kx);
    this.dx = x0 * Math.cos(kz) - y1 * Math.sin(kz);
    this.dy = x0 * Math.sin(kz) + y1 * Math.cos(kz);
    this.dz = z1;
    projectionTransformation();
  }
}
}}

* 解説 [#explanation]
- 頂点を表す`Vertex`クラスを`8`個使用して辺の長さが`100`の正六面体ワイヤーフレームを作成
#code{{
List<Vertex> cube = new ArrayList<>(8);
double sideLength = 100;
cube.add(new Vertex(sideLength, sideLength, sideLength));
cube.add(new Vertex(sideLength, sideLength, -sideLength));
cube.add(new Vertex(-sideLength, sideLength, -sideLength));
cube.add(new Vertex(-sideLength, sideLength, sideLength));
cube.add(new Vertex(sideLength, -sideLength, sideLength));
cube.add(new Vertex(sideLength, -sideLength, -sideLength));
cube.add(new Vertex(-sideLength, -sideLength, -sideLength));
cube.add(new Vertex(-sideLength, -sideLength, sideLength));
}}

- 視点を原点、スクリーンまでの距離を`500`、`z`軸座標値をスクリーンの奥へ`1000`移動して正六面体の各頂点を投影変換
- 変換した頂点座標を`Path2D`で繋いでワイヤーフレームを`JPanel`の中央に描画
#code{{
@Override protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  Graphics2D g2 = (Graphics2D) g.create();
  g2.setRenderingHint(
    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  Path2D path = new Path2D.Double();
  path.moveTo(cube.get(0).vx, cube.get(0).vy);
  path.lineTo(cube.get(1).vx, cube.get(1).vy);
  path.lineTo(cube.get(2).vx, cube.get(2).vy);
  path.lineTo(cube.get(3).vx, cube.get(3).vy);
  path.lineTo(cube.get(0).vx, cube.get(0).vy);
  path.lineTo(cube.get(4).vx, cube.get(4).vy);
  path.lineTo(cube.get(5).vx, cube.get(5).vy);
  path.lineTo(cube.get(6).vx, cube.get(6).vy);
  path.lineTo(cube.get(7).vx, cube.get(7).vy);
  path.lineTo(cube.get(4).vx, cube.get(4).vy);
  path.moveTo(cube.get(1).vx, cube.get(1).vy);
  path.lineTo(cube.get(5).vx, cube.get(5).vy);
  path.moveTo(cube.get(2).vx, cube.get(2).vy);
  path.lineTo(cube.get(6).vx, cube.get(6).vy);
  path.moveTo(cube.get(3).vx, cube.get(3).vy);
  path.lineTo(cube.get(7).vx, cube.get(7).vy);
  Rectangle r = SwingUtilities.calculateInnerArea(this, null);
  g2.setPaint(Color.WHITE);
  g2.fill(r);
  g2.translate(r.getCenterX(), r.getCenterY());
  g2.setPaint(Color.BLACK);
  g2.draw(path);
  g2.dispose();
}
}}

- 正六面体を投影変換して描画する`JPanel`に以下のような`MouseAdapter`を追加して各頂点を空間内で回転
#code{{
private class DragRotateHandler extends MouseAdapter {
  private final Cursor defCursor = Cursor.getDefaultCursor();
  private final Cursor hndCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
  private final Point pp = new Point();

  @Override public void mouseDragged(MouseEvent e) {
    Point pt = e.getPoint();
    double rotY = (pt.x - pp.x) * .03;
    double rotX = (pt.y - pp.y) * .03;
    double rotZ = 0d;
    for (Vertex v : cube) {
      v.rotateTransformation(rotX, rotY, rotZ);
    }
    pp.setLocation(pt);
    e.getComponent().repaint();
  }

  @Override public void mousePressed(MouseEvent e) {
    e.getComponent().setCursor(hndCursor);
    pp.setLocation(e.getPoint());
  }

  @Override public void mouseReleased(MouseEvent e) {
    e.getComponent().setCursor(defCursor);
  }
}
}}

* 参考リンク [#reference]
- [https://www.amazon.co.jp/dp/B09Q8WLRZ9 Designing Math. 数学とデザインをむすぶプログラミング入門 | 古堅 真彦 | 工学 | Kindleストア | Amazon]
- [https://apcs.mathorama.com/index.php?n=Main.Perspective APCS wiki - Perspective]
- [https://mathorama.com/apcs/pmwiki.php?n=Main.Perspective APCS wiki - Perspective]
- `jdk-8-windows-x64-demos.zip\jdk1.8.0_341\demo\jfc\Java2D\src.zip\java2d\demos\Colors\Rotator3D.java`
-- `Java 2D`デモの`Colors`タブの`demos.Colors.Rotator3D`に色付き正六面体が回転移動するアニメーションデモがある
-- [https://nowokay.hatenablog.com/entry/2022/12/09/164437 Java2Dデモを最新JDKで動かす - きしだのHatena]

* コメント [#comment]
#comment
#comment