I would like to form a shape between two updating points in 3D space using pyqtgraph and OpenGL. For now, I have only found it possible to connect GLLinePlotItem and GLMeshItem with vertexes and flat faces between two points. However, I would like to have an oval or cylinder form connected between the points, but I cannot seem to find a way to use the integrated MeshData sphere and cylinder, without jumping into complicated mathematics, rotation matrices and trigonometry.
Is there a simpler way, similar to GLLinePlotItem or GLMeshItem?
Illustration of what I have right now, and what I would like to have instead:
Sample code:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import numpy as np
import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QMainWindow, QApplication
from random import randint
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
w = gl.GLViewWidget()
w.show()
w.setCameraPosition(distance=15, azimuth=-90)
self.timer = QTimer()
self.timer.start(1000)
self.timer.timeout.connect(self.start)
g = gl.GLGridItem()
g.scale(2, 2, 1)
w.addItem(g)
self.md = gl.MeshData.sphere(rows=10, cols=20)
self.m1 = gl.GLMeshItem(meshdata=self.md,
smooth=True,
color=(1, 0, 0, 0.2),
shader="balloon",
glOptions="additive")
w.addItem(self.m1)
self.lineMesh = gl.GLLinePlotItem(width=1, antialias=False)
w.addItem(self.lineMesh)
def start(self):
# coordinates
point1 = np.array([randint(0,25), randint(0,25), 0])
point2 = np.array([randint(0,25), randint(0,25), 20])
line = np.array([point1, point2])
self.lineMesh.setData(pos=line)
length = (((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2 + (
point2[2] - point1[2]) ** 2) ** 0.5)*0.5
center = (point1 + point2) / 2
#radius = np.linalg.norm(point2 - point1) / 2
self.md = gl.MeshData.sphere(rows=10, cols=20, radius=[1])
self.m1.setMeshData(meshdata=self.md)
self.m1.resetTransform()
self.m1.scale(1, 1, length)
self.m1.translate(*center)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
I don't think you can avoid having to do a little math here, but the trig isn't too bad:
v = point2 - point1
theta = np.arctan2(v[1], v[0])
phi = np.arctan2(np.linalg.norm(v[:2]), v[2])
tr = pg.Transform3D()
tr.translate(*point1)
tr.rotate(theta * 180 / np.pi, 0, 0, 1)
tr.rotate(phi * 180 / np.pi, 0, 1, 0)
tr.scale(1, 1, np.linalg.norm(v) / 2)
tr.translate(0, 0, 1)
self.m1.setTransform(tr)
And if you prefer linear algebra rather than trigonometry, that's not too bad either, although a bit more verbose:
# pick 4 points on the untransformed sphere
a = np.array([
[0., 0., -1.],
[0., 0., 1.],
[1., 0., -1.],
[0., 1., -1.],
])
# and 4 corresponding points on the transformed sphere
v1 = np.cross(point1-point2, [0., 0., 1.])
v2 = np.cross(point1-point2, v1)
b = np.array([
point1,
point2,
point1 + v1 / np.linalg.norm(v1),
point1 + v2 / np.linalg.norm(v2),
])
# solve the transform mapping from a to b
tr = pg.solve3DTransform(a, b)
# make this transform work in opengl's homogeneous coordinate system
tr[3,3] = 1
self.m1.setTransform(tr)