HSP(Hot Soup Processor)で4次元図形を回転させて描画 - 雑感等
これの二番煎じだけどクラスの設計はMVCを意識したつもり.
4次元図形の回転は上の記事と同様
- https://ch.nicovideo.jp/4dimensions/blomaga/ar969330
- https://www.researchgate.net/publication/228571083_Projection_from_4D_to_3D
MVCについて参考になったサイト
- http://freedomtsubasa.hatenablog.com/entry/2017/12/07/012724
- https://qiita.com/tshinsay/items/5b1724baf32b8b5113c2
動作画面
一番上の行にある3つのスライダーは,4D->3Dに射影するカメラの角度.
その下の行にある2つのスライダーは,3D->2Dに射影するカメラの角度.
その下には描画された図形.この描画範囲内でホイールをコロコロすると,図形が拡大縮小する.
pythonソース
from PyQt5.QtWidgets import QWidget, QApplication, QSlider, QGraphicsView, QGraphicsScene, QVBoxLayout, QHBoxLayout, \ QGraphicsItem from PyQt5.QtGui import QPen from PyQt5.QtCore import Qt, QRectF import sys import numpy as np class Model(object): def __init__(self): self.lines = None self._init_lines() pass def _init_lines(self): """ 線(点座標の対)を初期化 点座標は4次元 :return: """ self.angle4d = [0, 0, 0] self.angle3d = [0, 0] p_o = np.array([0, 0, 0, 0]) p_x = np.array([1, 0, 0, 0]) p_y = np.array([0, 1, 0, 0]) p_z = np.array([0, 0, 1, 0]) p_w = np.array([0, 0, 0, 1]) p_any = np.array([1, 1, 1, 1]) p_any = p_any / np.linalg.norm(p_any) self.lines = np.stack([ np.vstack([p_o, p_x]), np.vstack([p_o, p_y]), np.vstack([p_o, p_z]), np.vstack([p_o, p_w]), np.vstack([p_o, p_any]) ]) pass def set_t(self, val): self.angle4d[0] = val pass def set_u(self, val): self.angle4d[1] = val pass def set_v(self, val): self.angle4d[2] = val pass def set_t3(self, val): self.angle3d[0] = val pass def set_u3(self, val): self.angle3d[1] = val pass def _calc_projection4d(self, point_4, angle4d_cam, angle3d_cam): """ 投影を計算 :param point_4: :param angle: :return: """ t, u, v = angle4d_cam t3, u3 = angle3d_cam st = np.sin(t) ct = np.cos(t) su = np.sin(u) cu = np.cos(u) sv = np.sin(v) cv = np.cos(v) st3 = np.sin(t3) ct3 = np.cos(t3) su3 = np.sin(u3) cu3 = np.cos(u3) proj_4to3 = np.array([ [-st, ct, 0, 0], [-ct * su, -st * su, cu, 0], [-ct * cu * sv, -st * cu * sv, -su * sv, cv] ]) proj_3to2 = np.array([ [-st3, ct3, 0], [-ct3 * su3, -st3 * su3, cu3], ]) return proj_3to2 @ proj_4to3 @ point_4 pass def projection(self): """ 投影された座標データを返す :return: """ angle4d = np.array(self.angle4d) angle3d = np.array(self.angle3d) # ↓でやりたいことは,行列の第2方向(第0,第1に次ぐ第2方向)の各要素(4要素のベクトル)に対して,_calc_projection4dを適用したい return np.apply_along_axis(lambda x: self._calc_projection4d(x, angle4d, angle3d), 2, self.lines) pass pass class Plotter(QGraphicsItem): def __init__(self, width, height, model): super(Plotter, self).__init__() self.width = width self.height = height self.model = model self.zoom = 200 pass def plottable_centre(self): return np.array([self.width / 2, self.height / 2, self.width / 2, self.height / 2]) pass def do_plot(self): lines = self.model.projection() centre = self.plottable_centre() self.plottable_lines = list(map(lambda x: list(map(int, x.flatten() * self.zoom + centre)), lines)) self.update() pass def paint(self, painter, option, widget): colours = [Qt.red, Qt.green, Qt.blue, Qt.black, Qt.darkCyan, Qt.darkYellow, Qt.darkYellow] line_colour = zip(self.plottable_lines, colours) for line, colour in line_colour: pen = QPen() pen.setWidth(3) pen.setBrush(colour) painter.setPen(pen) painter.drawLine(*line) pass pass def boundingRect(self): return QRectF(0, 0, self.width, self.height) pass def wheelEvent(self, event): d = event.delta() self.zoom += d / 10 self.do_plot() pass pass class View(QWidget): def __init__(self, model): super(View, self).__init__() self.model = model pass def register(self, controller): self.controller = controller self.init_ui() pass def init_ui(self): """ ウィジェットの配置 :return: """ self.setWindowTitle("4D rotation") self.scene_size = 500 self.plotter = Plotter(self.scene_size, self.scene_size, self.model) controllers_4d_layout = QHBoxLayout() controllers_4d = self.controller.controllers_4d for ix in range(len(controllers_4d)): sld = QSlider(Qt.Horizontal, self) sld.setValue(33) controllers_4d_layout.addWidget(sld) sld.valueChanged.connect(controllers_4d[ix]) controllers_4d[ix](sld.value()) pass controllers_3d_layout = QHBoxLayout() controllers_3d = self.controller.controllers_3d for ix in range(len(controllers_3d)): sld = QSlider(Qt.Horizontal, self) sld.setValue(33) controllers_3d_layout.addWidget(sld) sld.valueChanged.connect(controllers_3d[ix]) controllers_3d[ix](sld.value()) pass self.graphics_view = QGraphicsView() scene = QGraphicsScene(self.graphics_view) scene.setSceneRect(0, 0, self.scene_size, self.scene_size) self.graphics_view.setScene(scene) scene.addItem(self.plotter) # self.setLayout(grid) main_layout = QVBoxLayout() main_layout.setAlignment(Qt.AlignTop) main_layout.addLayout(controllers_4d_layout) main_layout.addLayout(controllers_3d_layout) main_layout.addWidget(self.graphics_view) self.setLayout(main_layout) pass pass class Controller(object): def __init__(self, view, model): self.view = view self.model = model self.controllers_4d = [self.chg_sld_t, self.chg_sld_u, self.chg_sld_v] self.controllers_3d = [self.chg_sld_t3, self.chg_sld_u3] self.view.register(self) self.do_plot() pass def do_plot(self): self.view.plotter.do_plot() pass def chg_sld_t(self, value): self.model.set_t(value * 2 * np.pi / 100) self.do_plot() pass def chg_sld_u(self, value): self.model.set_u(value * 2 * np.pi / 100) self.do_plot() pass def chg_sld_v(self, value): self.model.set_v(value * 2 * np.pi / 100) self.do_plot() pass def chg_sld_t3(self, value): self.model.set_t3(value * 2 * np.pi / 100) self.do_plot() pass def chg_sld_u3(self, value): self.model.set_u3(value * 2 * np.pi / 100) self.do_plot() pass pass def main(): app = QApplication(sys.argv) model = Model() view = View(model) controller = Controller(view, model) view.show() sys.exit(app.exec_()) pass if __name__ == '__main__': main() pass
PC変えてからHSPインタプリタ入れてなくて,入れるのめんどくさかった.
pythonでのgui開発とオブジェクト指向っぽい書き方の勉強がてらpythonに移植?した.
PlotterクラスとViewクラスに分けて実装した(参考にしたサイト・ソース↓がQGraphicsItemのクラスとViewクラスに分けてた)がMVC的には良かったのかわからない.