HSP(Hot Soup Processor)で4次元図形を回転させて描画 - 雑感等
これの二番煎じだけどクラスの設計はMVCを意識したつもり.
4次元図形の回転は上の記事と同様
MVCについて参考になったサイト
動作画面
一番上の行にある3つのスライダーは,4D->3Dに射影するカメラの角度.
その下の行にある2つのスライダーは,3D->2Dに射影するカメラの角度.
その下には描画された図形.この描画範囲内でホイールをコロコロすると,図形が拡大縮小する.
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)
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)
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的には良かったのかわからない.