I want to do a project that displays a planet with continents and climatic zones.
I am currently using PyQt5 for painting, with QPainterPath:
There are two problems:
With this approach, it's hard to define zones inside the continent.
Painting is quite slow (calculations are fast, thanks to numpy).
Minimal code for Path creating way:
import sys
import time
import numpy as np
import math
from PyQt5.QtWidgets import QWidget, QApplication, QSlider
from PyQt5.QtGui import QPainter, QPainterPath, QPen
from PyQt5.QtCore import Qt
class TwoDMap(QWidget):
def __init__(self, size):
super().__init__()
self.size = size
self.W = self.size.width() # width of screen
self.H = self.size.height() # height of screen
self.qp = QPainter()
self.circleRadius = self.H/3
self.pointsAcc = 30 # defines how many points will be between main points
self.interface_start_point = [50, 50]
self.circle_start_x = self.interface_start_point[0] + 100
self.circle_start_y = self.interface_start_point[1] + 200
# vars for converting points from math syster to screen system
self.scrcoord = np.array([self.circle_start_x + self.circleRadius ,
self.circle_start_y + self.circleRadius, 0])
self.scrcoordcoef = np.array([1, -1, 1])
######### Vars for rotatiob ###############
# angles
self.rotation_x = 0
self.rotation_y = 0
self.rotation_z = 0
self.axis_x = np.array([1, 0, 0])
self.axis_y = np.array([0, 1, 0])
self.axis_z = np.array([0, 0, 1])
############################################
# main point of star
points = np.array([
[self.circleRadius, math.pi/10 , -math.pi/10, ],
[self.circleRadius, math.pi/4.5 , math.pi/5.5, ],
[self.circleRadius, math.pi/3 , 0 , ],
[self.circleRadius, math.pi/3 , math.pi/5, ],
[self.circleRadius, math.pi/2 , math.pi/4, ],
[self.circleRadius, math.pi/2.75, math.pi/3, ],
[self.circleRadius, math.pi/2.75, math.pi/2, ],
[self.circleRadius, math.pi/4 , 4*math.pi/10, ],
[self.circleRadius, math.pi/5.5 , 5.5*math.pi/8,],
[self.circleRadius, math.pi/6 , math.pi/3, ],
])
# convert to decart coordinates
self.points = np.apply_along_axis(self.sphericToDecart, 1, points)
self.initUI()
self.showFullScreen()
def initUI(self):
self.setWindowTitle('2DMap')
self.setStyleSheet("background-color: black; color: white; font-size: 20px;")
# sliders for rotating
self.angleScale = 100
self.polz1 = QSlider(Qt.Horizontal, self)
self.polz1.setGeometry(self.interface_start_point[0] + 10, self.interface_start_point[1] + 10, 200, 30)
self.polz1.setRange(int(- 2 * math.pi * self.angleScale), int(2 * math.pi * self.angleScale))
self.polz1.setValue(0)
self.polz1.valueChanged.connect(self.angleChanged)
self.polz2 = QSlider(Qt.Horizontal, self)
self.polz2.setGeometry(self.interface_start_point[0] + 10, self.interface_start_point[1] + 40, 200, 30)
self.polz2.setRange(int(- 2 * math.pi * self.angleScale), int(2 * math.pi * self.angleScale))
self.polz2.setValue(0)
self.polz2.valueChanged.connect(self.angleChanged)
self.polz3 = QSlider(Qt.Horizontal, self)
self.polz3.setGeometry(self.interface_start_point[0] + 10, self.interface_start_point[1] + 70, 200, 30)
self.polz3.setRange(int(- 2 * math.pi * self.angleScale), int(2 * math.pi * self.angleScale))
self.polz3.setValue(0)
self.polz3.valueChanged.connect(self.angleChanged)
def angleChanged(self):
# func for rotating picture with slider
self.rotation_x = self.polz1.value()/(2*self.angleScale)
self.rotation_y = self.polz2.value()/(2*self.angleScale)
self.rotation_z = self.polz3.value()/(2*self.angleScale)
self.update()
def paintEvent(self, e):
self.qp.begin(self)
self.qp.setRenderHints(QPainter.Antialiasing)
self.qp.drawEllipse(int(self.circle_start_x), int(self.circle_start_y), int(self.circleRadius*2), int(self.circleRadius*2))
self.qp.setPen(QPen(Qt.green, 3, Qt.SolidLine))
path_obj = self.getSpherePath(self.getPointsToDraw())
self.qp.drawPath(path_obj)
self.qp.end()
def getPointsToDraw(self):
points: np.ndarray = self.allPointsRotationOffset(self.points)
# preparations for computing points
nw = points
nw2 = np.zeros(points.shape)
nw2[-1:] = points[0]
nw2[:-1] = points[1::1]
crs2 = np.cross(np.cross(nw, nw2), nw)
crs2 = (crs2.T / np.sqrt(np.sum(crs2 * crs2, axis=1)) * self.circleRadius).T
alphas = np.arcsin(np.sqrt(np.sum((nw2 - nw) * (nw2 - nw), axis=1)) / (2 * self.circleRadius)) * 2
# determine the number of points depending on the angle between them
endp = ((self.pointsAcc+1) * (alphas / (np.pi / 2)))
endp: np.ndarray = endp.astype(np.int16)
pnts = [0] * points.shape[0]
for i in range(points.shape[0]):
rngs = range(endp[i]+1) * alphas[i] / endp[i]
rng_cos = np.cos(rngs)
rng_sin = np.sin(rngs)
pnts[i] = self.getInterPoints(nw[i], crs2[i], rng_cos, rng_sin)
return pnts
def getSpherePath(self, allpoints):
# func for creating actual path, that will be drawn on screen
path = QPainterPath()
first_point = True
line_break = False
for points in allpoints: # for all point group
for point in points: # for all points in group
# drawing only points with z > 0
if (point[2] > 0):
if first_point:
# for first point we need to move to it
path.moveTo(point[0], point[1])
first_point = False
if line_break:
# now we have point with z > 0, but before this we had point with z < 0
# that means we need to move to new part
path.moveTo(point[0], point[1])
line_break = False
continue
# connect points (z > 0) with simple line
path.lineTo(point[0], point[1])
else:
# we need to disconect line, because point have z < 0 and must not be shown
line_break = True
return path
def getInterPoints(self, basis_i, basis_j, rng_cos, rng_sin):
# function computes points between two main points
# and covert them to screen coord. system with center on sphere center
arr = np.outer(basis_i, rng_cos) + np.outer(basis_j, rng_sin)
return arr.T * self.scrcoordcoef + self.scrcoord
def allPointsRotationOffset(self, points: np.ndarray) -> np.ndarray:
# Rotate points along axises with angles: rotation_z, rotation_y, rotation_x
rotated_pointsZ = self.kvantRotation(points, self.axis_z, self.rotation_z)
rotated_pointsY = self.kvantRotation(rotated_pointsZ, self.axis_y, self.rotation_y)
rotated_pointsX = self.kvantRotation(rotated_pointsY, self.axis_x, self.rotation_x)
return rotated_pointsX
def kvantRotation(self, p: np.ndarray, u, f):
sin = np.sin(f/2)
Sa = np.cos(f/2)
q = sin * u
qp0 = np.matmul(p, -1 * q)
ABC = np.cross(q, p) + Sa * p
qqp = np.outer(qp0, q)
prm = -1 * np.cross(ABC, q) - qqp
trr = Sa * ABC
ans2 = prm + trr
return ans2
def sphericToDecart(self, point):
# Convert points from spheric system to decart system
xyz = np.array([
math.sin(point[1]) * math.cos(point[2]),
math.sin(point[1]) * math.sin(point[2]),
math.cos(point[1])
]) * point[0]
return xyz
def start():
app = QApplication(sys.argv)
ex = TwoDMap(app.primaryScreen().size())
app.exec_()
if __name__ == '__main__':
start()
So I tried another method: using triangles for painting continents:
But this approach is MUCH slower than I thought it would be and slower than the previous method.
Code for triangles:
class Example(QWidget):
def __init__(self):
super().__init__()
self.W = 800
self.H = 800
self.CoF = 50
self.dx = 0
self.initUI()
def initUI(self):
self.setGeometry(50, 50, self.W, self.H)
self.setWindowTitle('Zamo')
self.poligons = []
self.poligons_points = []
self.triangleZam()
self.show()
#profile
def paintEvent(self, e):
start = time.time()
qp = QPainter()
qp.begin(self)
qp.setRenderHints(QPainter.Antialiasing)
pointsF = [
QPoint(430 - 300 + 200, 370 - 300 + 200),
QPoint(440 - 300 + 200, 500 - 300 + 200),
QPoint(260 - 300 + 200, 550 - 300 + 200),
QPoint(420 - 300 + 200, 600 - 300 + 200),
QPoint(420 - 300 + 200, 800 - 300 + 200),
QPoint(530 - 300 + 200, 640 - 300 + 200),
QPoint(680 - 300 + 200, 740 - 300 + 200),
QPoint(630 - 300 + 200, 560 - 300 + 200),
QPoint(720 - 300 + 200, 430 - 300 + 200),
QPoint(570 - 300 + 200, 450 - 300 + 200),
]
main_poli = QPolygon(pointsF)
main_rect: QRect = main_poli.boundingRect()
qp.setBrush(Qt.lightGray)
qp.setPen(QPen(QColor(0, 0, 0, 255), 1, Qt.SolidLine)) # Qt.lightGray QColor(0, 0, 0, 0)
# self.poligons - list of QPoligon's
# self.poligons_points - list of point of poligons (used for optimization)
# main_poli - star
# main_rect - QRect, bounding the star
for i in range(len(self.poligons)):
qp.setBrush(Qt.lightGray)
if main_rect.contains(self.poligons_points[i][0]): # if triangle inside the Rect, then check for intersecting
if main_poli.intersects(self.poligons[i]):
qp.setBrush(Qt.green)
qp.drawPolygon(self.poligons[i])
qp.setBrush(QColor(0, 0, 0, 0))
qp.setPen(QPen(QColor(255, 0, 0, 150), 1, Qt.SolidLine))
qp.drawPolygon(main_poli)
qp.end()
print(time.time() - start)
def triangleZam(self):
r = int(self.W/self.CoF)
midDot = r / np.sqrt(3)
a, b = 0 - r, 0
c, d = r/2 - r, midDot
e, f = r - r, 0
for j in range(int(self.H/midDot + 1)):
a = 0 - r / (j % 2 + 1)
c = r / 2 - r / (j % 2 + 1)
e = r - r / (j % 2 + 1)
for i in range(int(self.CoF + 1)):
points = QPolygon([QPoint(a, b),QPoint(c, d),QPoint(e, f)])
#qp.setBrush(Qt.darkGray)
points2 = QPolygon([QPoint(c, d),QPoint(e, f),QPoint(c+r, d)])
self.poligons.append(points)
self.poligons.append(points2)
self.poligons_points.append([QPoint(a, b),QPoint(c, d),QPoint(e, f)])
self.poligons_points.append([QPoint(c, d),QPoint(e, f),QPoint(c+r, d)])
a = a + r
c = c + r
e = e + r
b = b + midDot
d = d + midDot
f = f + midDot
So, my question is: is there some way to achieve faster painting in python (algorithms, libraries)? Or maybe is there some resource I can study about 2D rendering basics?
Related
I try smth like this:
def main_rec():
width = random.randint(150, 250)
height = random.randint(150, 250)
angle = rand_angle()
c, s = np.cos(angle), np.sin(angle)
R = np.array(((c, -s), (s, c)))
center = (random.randint(0, 640), random.randint(0, 480))
x1y10 = (center[0] - width / 2, center[1] + height / 2)
x2y20 = (x1y10[0] + width, x1y10[1])
x3y30 = (x2y20[0], x2y20[1] - height)
x4y40 = (x3y30[0] - width, x3y30[1])
x1y1 = (x1y10[0] * R[0][0] + x1y10[1] * R[0][1], x1y10[0] * R[1][0] + x1y10[1] * R[1][1])
x2y2 = (x2y20[0] * R[0][0] + x2y20[1] * R[0][1], x1y10[1] * R[0][1] + x2y20[1] * R[1][1])
x3y3 = (x3y30[0] * R[0][0] + x3y30[1] * R[0][1], x3y30[0] * R[1][0] + x3y30[1] * R[1][1])
x4y4 = (x4y40[0] * R[0][0] + x4y40[1] * R[0][1], x4y40[1] * R[0][1] + x4y40[1] * R[1][1])
points = [x1y1, x2y2, x3y3, x4y4]
return points, angle / 3.14159 * 180
but I don't know how to set a condition for the corners to be right. I try to use rotation matrix. It makes normal rectangles only for angle = 0
using numpy and rotation matrix code would be:
import numpy as np
import matplotlib.pyplot as plt
def create_rect(width,height,center):
x,y = center
w,h = width,height
return np.array([[x-w/2,y-h/2],
[x+w/2,y-h/2],
[x+w/2,y+h/2],
[x-w/2,y+h/2],
[x-w/2,y-h/2]]).T
width = np.random.randint(150,250)
height = np.random.randint(150,250)
center = (np.random.randint(0, 640), np.random.randint(0, 480))
angle = np.random.randint(0,360)/360.0*2*np.pi
rotmat = np.array([[np.cos(angle),-np.sin(angle)],
[np.sin(angle),np.cos(angle)]])
rect = create_rect(width,height,center)
rot_rect = rotmat # rect
plt.imshow(*rot_rect)
So I created this parabola class which can be instantiated with 3 parameters (a, b and c) or with 3 points belonging to the parabola. The punti() function returns all the points belonging to the parabola in a range defined by n and m. Here's the code (Most of this is in Italian, sorry):
class Parabola:
def __init__(self, tipo=0, *params):
'''
Il tipo è 0 per costruire la parabola con a, b, c; 1 per costruire la parabola con
tre punti per la quale passa
'''
if tipo == 0:
self.__a = params[0]
self.__b = params[1]
self.__c = params[2]
self.__delta = self.__b ** 2 - (4 * self.__a * self.__c)
elif tipo == 1:
matrix_a = np.array([
[params[0][0]**2, params[0][0], 1],
[params[1][0]**2, params[1][0], 1],
[params[2][0]**2, params[2][0], 1]
])
matrix_b = np.array([params[0][1], params[1][1], params[2][1]])
matrix_c = np.linalg.solve(matrix_a, matrix_b)
self.__a = round(matrix_c[0], 2)
self.__b = round(matrix_c[1], 2)
self.__c = round(matrix_c[2], 2)
self.__delta = self.__b ** 2 - (4 * self.__a * self.__c)
def trovaY(self, x):
y = self.__a * x ** 2 + self.__b * x + self.__c
return y
def punti(self, n, m, step=1):
output = []
for x in range(int(min(n, m)), int(max(n, m)) + 1, step):
output.append((x, self.trovaY(x)))
return output
Now my little game is about shooting targets with a bow and i have to use the parabola for the trajectory and it passes by 3 points:
The player center
A point with the cursor's x and player's y
A point in the middle with the cursors's y
The trajectory is represented by a black line but it clearly doesn't work and I can't understand why. Here's the code of the game (Don't mind about the bow's rotation, I still have to make it function properly):
import os
import sys
import pygame
from random import randint
sys.path.insert(
1, __file__.replace("pygame-prototype\\" + os.path.basename(__file__), "coniche\\")
)
import parabola
# Initialization
pygame.init()
WIDTH, HEIGHT = 1024, 576
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# Function to rotate without losing quality
def rot_from_zero(surface, angle):
rotated_surface = pygame.transform.rotozoom(surface, angle, 1)
rotated_rect = rotated_surface.get_rect()
return rotated_surface, rotated_rect
# Function to map a range of values to another
def map_range(value, leftMin, leftMax, rightMin, rightMax):
# Figure out how 'wide' each range is
leftSpan = leftMax - leftMin
rightSpan = rightMax - rightMin
# Convert the left range into a 0-1 range (float)
valueScaled = float(value - leftMin) / float(leftSpan)
# Convert the 0-1 range into a value in the right range.
return rightMin + (valueScaled * rightSpan)
# Player class
class Player:
def __init__(self, x, y, width=64, height=64):
self.rect = pygame.Rect(x, y, width, height)
self.dirx = 0
self.diry = 0
def draw(self):
rectangle = pygame.draw.rect(screen, (255, 0, 0), self.rect)
# Target class
class Target:
def __init__(self, x, y, acceleration=0.25):
self.x, self.y = x, y
self.image = pygame.image.load(
__file__.replace(os.path.basename(__file__), "target.png")
)
self.speed = 0
self.acceleration = acceleration
def draw(self):
screen.blit(self.image, (self.x, self.y))
def update(self):
self.speed -= self.acceleration
self.x += int(self.speed)
if self.speed < -1:
self.speed = 0
player = Player(64, HEIGHT - 128)
# Targets init
targets = []
targets_spawn_time = 3000
previous_ticks = pygame.time.get_ticks()
# Ground animation init
ground_frames = []
for i in os.listdir(__file__.replace(os.path.basename(__file__), "ground_frames")):
ground_frames.append(
pygame.image.load(
__file__.replace(os.path.basename(__file__), "ground_frames\\" + i)
)
) # Load all ground frames
ground_frame_counter = 0 # Keep track of the current ground frame
frame_counter = 0
# Bow
bow = pygame.image.load(__file__.replace(os.path.basename(__file__), "bow.png"))
angle = 0
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# Spawning the targets
current_ticks = pygame.time.get_ticks()
if current_ticks - previous_ticks >= targets_spawn_time:
targets.append(Target(WIDTH, randint(0, HEIGHT - 110)))
previous_ticks = current_ticks
screen.fill((101, 203, 214))
player.draw()
for i, e in list(enumerate(targets))[::-1]:
e.draw()
e.update()
if e.x <= -e.image.get_rect().width:
del targets[i]
# Calculating the angle of the bow
mouse_pos = pygame.Vector2(pygame.mouse.get_pos())
angle = map_range(mouse_pos.x, 0, WIDTH, 90, 0)
# Rotate the bow
rotated_bow, rotated_bow_rect = rot_from_zero(bow, angle)
rotated_bow_rect.center = player.rect.center
screen.blit(rotated_bow, rotated_bow_rect)
# Animate the ground
if frame_counter % 24 == 0:
ground_frame_counter += 1
if ground_frame_counter >= len(ground_frames):
ground_frame_counter = 0
for i in range(round(WIDTH / ground_frames[ground_frame_counter].get_rect().width)):
screen.blit(
ground_frames[ground_frame_counter],
(
ground_frames[ground_frame_counter].get_rect().width * i,
HEIGHT - ground_frames[ground_frame_counter].get_rect().height,
),
)
# Calculating the trajectory
mouse_pos.x = (
mouse_pos.x if mouse_pos.x != rotated_bow_rect.centerx else mouse_pos.x + 1
)
# print(mouse_pos, rotated_bow_rect.center)
v_x = rotated_bow_rect.centerx + ((mouse_pos.x - rotated_bow_rect.centerx) / 2)
trajectory_parabola = parabola.Parabola(
1,
rotated_bow_rect.center,
(mouse_pos.x, rotated_bow_rect.centery),
(v_x, mouse_pos.y),
)
trajectory = [(i[0], int(i[1])) for i in trajectory_parabola.punti(0, WIDTH)]
pygame.draw.lines(screen, (0, 0, 0), False, trajectory)
pygame.draw.ellipse(
screen, (128, 128, 128), pygame.Rect(v_x - 15, mouse_pos.y - 15, 30, 30)
)
pygame.draw.ellipse(
screen,
(128, 128, 128),
pygame.Rect(mouse_pos.x - 15, rotated_bow_rect.centery - 15, 30, 30),
)
pygame.display.update()
if frame_counter == 120:
for i in trajectory:
print(i)
frame_counter += 1
You can run all of this and understand what's wrong with it, help?
You round the values of a, b and c to 2 decimal places. This is too inaccurate for this application:
self.__a = round(matrix_c[0], 2)
self.__b = round(matrix_c[1], 2)
self.__c = round(matrix_c[2], 2)
self.__a = matrix_c[0]
self.__b = matrix_c[1]
self.__c = matrix_c[2]
Similar to answer above... rounding is the issue here. This is magnified when the scale of the coordinates gets bigger.
However, disagree with other solution: It does not matter what order you pass the coordinates into your parabola construction. Any order works fine. points are points.
Here is a pic of your original parabola function "drooping" because of rounding error:
p1 = (0, 10) # left
p2 = (100, 10) # right
p3 = (50, 100) # apex
p = Parabola(1, p3, p2, p1)
traj = p.punti(0, 100)
xs, ys = zip(*traj)
plt.scatter(xs, ys)
plt.plot([0, 100], [10, 10], color='r')
plt.show()
I have created these triangles to form the greyscale surface of 2-D line plot. Now when I am rotating it using mouse wheel event, there is a gap between the top and bottom line and I want to remove it. Also the program run very slow after using this for loop in greyscale function. Can anyone suggest me any method or way to optimize this and make it work correctly?
code:-
import OpenGL.GL as gl
import OpenGL.arrays.vbo as glvbo
from PyQt5.Qt import *
import numpy as np
import sys
import copy
VS1 = '''
#version 450
layout(location = 0) in vec2 position;
uniform float right;
uniform float bottom;
uniform float left;
uniform float top;
void main() {
const float far = 1.0;
const float near = -1.0;
mat4 testmat = mat4(
vec4(2.0 / (right - left), 0, 0, 0),
vec4(0, 2.0 / (top - bottom), 0, 0),
vec4(0, 0, -2.0 / (far - near), 0),
vec4(-(right + left) / (right - left), -(top + bottom) / (top - bottom), -(far + near) / (far - near), 1)
);
gl_Position = testmat * vec4(position.x, position.y, 0., 1.);
}
'''
FS1 = '''
#version 450
// Output variable of the fragment shader, which is a 4D vector containing the
// RGBA components of the pixel color.
uniform vec3 triangleColor;
out vec4 outColor;
void main()
{
outColor = vec4(triangleColor, 1.0);
}
'''
VS = '''
#version 450
attribute vec2 position;
attribute vec3 a_Color;
uniform float right;
uniform float bottom;
uniform float left;
uniform float top;
out vec3 g_color;
void main() {
const float far = 1.0;
const float near = -1.0;
mat4 testmat = mat4(
vec4(2.0 / (right - left), 0, 0, 0),
vec4(0, 2.0 / (top - bottom), 0, 0),
vec4(0, 0, -2.0 / (far - near), 0),
vec4(-(right + left) / (right - left), -(top + bottom) / (top - bottom), -(far + near) / (far - near), 1)
);
gl_Position = testmat * vec4(position.x, position.y, 0., 1.);
g_color = a_Color;
}
'''
FS = '''
#version 450
// Output variable of the fragment shader, which is a 4D vector containing the
// RGBA components of the pixel color.
in vec3 g_color;
out vec4 outColor;
void main()
{
outColor = vec4(g_color, 1.0);
}
'''
def compile_vertex_shader(source):
"""Compile a vertex shader from source."""
vertex_shader = gl.glCreateShader(gl.GL_VERTEX_SHADER)
gl.glShaderSource(vertex_shader, source)
gl.glCompileShader(vertex_shader)
# check compilation error
result = gl.glGetShaderiv(vertex_shader, gl.GL_COMPILE_STATUS)
if not (result):
raise RuntimeError(gl.glGetShaderInfoLog(vertex_shader))
return vertex_shader
def compile_fragment_shader(source):
"""Compile a fragment shader from source."""
fragment_shader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
gl.glShaderSource(fragment_shader, source)
gl.glCompileShader(fragment_shader)
result = gl.glGetShaderiv(fragment_shader, gl.GL_COMPILE_STATUS)
if not (result):
raise RuntimeError(gl.glGetShaderInfoLog(fragment_shader))
return fragment_shader
def link_shader_program(vertex_shader, fragment_shader):
"""Create a shader program with from compiled shaders."""
program = gl.glCreateProgram()
gl.glAttachShader(program, vertex_shader)
gl.glAttachShader(program, fragment_shader)
gl.glLinkProgram(program)
result = gl.glGetProgramiv(program, gl.GL_LINK_STATUS)
if not (result):
raise RuntimeError(gl.glGetProgramInfoLog(program))
return program
class GLPlotWidget(QGLWidget):
def __init__(self, *args):
super(GLPlotWidget, self).__init__()
self.width, self.height = 100, 100
self.we = np.load('two.npy', mmap_mode='r')
self.e = copy.deepcopy(self.we[:, :, :])
self.right, self.left, self.top, self.bottom = self.e[0, -1, 0], self.e[
0, 0, 0], self.e[0, :, 1].max(), self.e[-1, :, 1].min()
self.vbo = glvbo.VBO(self.e)
self.count = self.vbo.shape[1]
self.scroll = 0
self.number_of_arm = 24
self.linerange = [(self.e[li, :, 1].max(), self.e[-li, :, 1].min()) for li in range(self.vbo.shape[0])]
self.showMaximized()
def initializeGL(self):
vs = compile_vertex_shader(VS1)
fs = compile_fragment_shader(FS1)
self.shaders_program_plot = link_shader_program(vs, fs)
self.greyscale_data()
def greyscale_data(self):
self.color = np.zeros((self.e.shape[1] * (self.e.shape[0]), 3), dtype=np.float32)
for i in range(0, 24):
a = self.e[i, :, 1].min()
b = self.e[i, :, 1].max()
c = np.interp(self.e[i, :, 1], (a, b), (0.15, 0.85))
self.color[self.e.shape[1] * i:self.e.shape[1] * (i + 1), 0] = c
self.color[self.e.shape[1] * i:self.e.shape[1] * (i + 1), 1] = c
self.color[self.e.shape[1] * i:self.e.shape[1] * (i + 1), 2] = c
self.elems = []
b = self.e.shape[1] # number of points per line
a = self.e.shape[0] # total number of arms
for i in range(0, a):
if i < a-1:
for j in range(0, b - 1):
self.elems += [j + b * i, j + b * i + 1, j + b * (i + 1)]
self.elems += [j + b * (i + 1), j + b * (i + 1) + 1, j + b * i + 1]
else:
for j in range(0, b - 1):
self.elems += [j + b * i, j + b * i + 1, j]
self.elems += [j, j + 1, j + b * i + 1]
self.elems = np.array(self.elems, dtype=np.int32)
# print(self.elems[0:100])
vs = compile_vertex_shader(VS)
fs = compile_fragment_shader(FS)
self.shaders_program = link_shader_program(vs, fs)
self.vertexbuffer = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertexbuffer)
gl.glBufferData(gl.GL_ARRAY_BUFFER, self.e, gl.GL_DYNAMIC_DRAW)
self.elementbuffer = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.elementbuffer)
gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, self.elems, gl.GL_DYNAMIC_DRAW)
self.colorbuffer = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.colorbuffer)
gl.glBufferData(gl.GL_ARRAY_BUFFER, self.color, gl.GL_DYNAMIC_DRAW)
def ortho_view(self, i):
right = gl.glGetUniformLocation(i, "right")
gl.glUniform1f(right, self.right)
left = gl.glGetUniformLocation(i, "left")
gl.glUniform1f(left, self.left)
top = gl.glGetUniformLocation(i, "top")
gl.glUniform1f(top, self.top)
bottom = gl.glGetUniformLocation(i, "bottom")
gl.glUniform1f(bottom, self.bottom)
def greyscale(self):
gl.glUseProgram(self.shaders_program)
self.ortho_view(self.shaders_program)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertexbuffer)
stride = 0 # 3*self.e.itemsize
offset = None # ctypes.c_void_p(0)
loc = gl.glGetAttribLocation(self.shaders_program, 'position')
gl.glEnableVertexAttribArray(loc)
gl.glVertexAttribPointer(loc, 2, gl.GL_FLOAT, False, stride, offset)
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.elementbuffer)
loc = gl.glGetAttribLocation(self.shaders_program, 'a_Color')
gl.glEnableVertexAttribArray(loc)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.colorbuffer)
gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
loc_top1 = gl.glGetUniformLocation(self.shaders_program, "top")
loc_bottom1 = gl.glGetUniformLocation(self.shaders_program, "bottom")
for i in range(0, 24):
size = self.top - self.bottom
top, bottom = self.top + self.scroll, self.bottom + self.scroll
if self.linerange[i][0] - self.scroll < self.bottom:
top, bottom = top - size, bottom - size
gl.glUniform1f(loc_top1, top)
gl.glUniform1f(loc_bottom1, bottom)
a = int(i * self.elems.size)
b = int((i+1) * self.elems.size)
c = int(self.elems.size/24)
# gl.glDrawElements(gl.GL_TRIANGLE_STRIP, self.elems.size, gl.GL_UNSIGNED_INT, None)
gl.glDrawRangeElements(gl.GL_TRIANGLE_STRIP, a, b, self.elems.size, gl.GL_UNSIGNED_INT, None)
def paintGL(self):
self.resizeGL(self.width, self.height)
gl.glClearColor(1, 1, 1, 0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
gl.glEnable(gl.GL_DEPTH_TEST)
self.vbo.bind()
gl.glEnableVertexAttribArray(0)
gl.glVertexAttribPointer(0, 2, gl.GL_FLOAT, gl.GL_FALSE, 0, None)
gl.glUseProgram(self.shaders_program_plot)
self.ortho_view(self.shaders_program_plot)
uni_color = gl.glGetUniformLocation(self.shaders_program_plot, "triangleColor")
loc_top = gl.glGetUniformLocation(self.shaders_program_plot, "top")
loc_bottom = gl.glGetUniformLocation(self.shaders_program_plot, "bottom")
for i in range(0, self.vbo.data.shape[0]):
size = self.top - self.bottom
top, bottom = self.top + self.scroll, self.bottom + self.scroll
if self.linerange[i][0] - self.scroll < self.bottom:
top, bottom = top - size, bottom - size
gl.glUniform1f(loc_top, top)
gl.glUniform1f(loc_bottom, bottom)
gl.glUniform3f(uni_color, 0, 0, 0)
gl.glLineWidth(1)
gl.glDrawArrays(gl.GL_LINE_STRIP, i * self.count, self.count)
self.vbo.unbind()
self.greyscale()
# gl.glUseProgram(0)
def resizeGL(self, width, height):
self.width, self.height = width, height
gl.glViewport(0, 0, width, height)
def wheelEvent(self, *args, **kwargs):
event = args[0]
scroll_scale = 0.01
size = self.top - self.bottom
if event.angleDelta().y() > 0:
self.scroll = self.scroll - size * scroll_scale
if self.scroll < 0:
self.scroll += size
else:
self.scroll = self.scroll + size * scroll_scale
if self.scroll > size:
self.scroll -= size
self.update()
def main():
app = QApplication(sys.argv)
editor = GLPlotWidget()
editor.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
data file:- https://drive.google.com/file/d/1y6w35kuMguR1YczK7yMJpXU86T6qtGSv/view?usp=sharing
Add a straight line at the at the bin and the end of the data.
Compute the minimum and the maximum of the original data, the y scale of the data and the average offset from one line to another:
origshape = self.e.shape[:]
origmin, origmax = self.e[0, :, 1].max(), self.e[-1, :, 1].min()
origsize = origmax - origmin
origoffset = origsize / origshape[0]
Compute a new minimum and maximum with an certain offset (origoffset/2) and add a straight line at the begin and the end. Copy the first and last line and change the y component of the new lines by newmax respectively newmin
newmin, newmax = origmin - origoffset/2, origmax + origoffset/2
self.first = self.e[0,:,:].copy().reshape((1, *origshape[1:]))
self.last = self.e[-1,:,:].copy().reshape((1, *origshape[1:]))
self.first[:,:,1] = newmax
self.last[:,:,1] = newmin
self.e = np.concatenate((self.first, self.e, self.last))
New constructor of GLPlotWidget:
class GLPlotWidget(QGLWidget):
def __init__(self, *args):
super(GLPlotWidget, self).__init__()
self.width, self.height = 100, 100
self.we = np.load('two.npy', mmap_mode='r')
self.e = copy.deepcopy(self.we[:, :, :])
origshape = self.e.shape[:]
origmin, origmax = self.e[-1, :, 1].min(), self.e[1, :, 1].max()
origsize = origmax - origmin
origoffset = origsize / origshape[0]
newmin, newmax = origmin - origoffset/2, origmax + origoffset/2
self.first = self.e[0,:,:].copy().reshape((1, *origshape[1:]))
self.last = self.e[-1,:,:].copy().reshape((1, *origshape[1:]))
self.first[:,:,1] = newmax
self.last[:,:,1] = newmin
self.e = np.concatenate((self.first, self.e, self.last))
self.right, self.left, self.top, self.bottom = self.e[0, -1, 0], self.e[
0, 0, 0], self.e[0, :, 1].max(), self.e[-1, :, 1].min()
self.vbo = glvbo.VBO(self.e)
self.count = self.vbo.shape[1]
self.scroll = 0
self.number_of_arm = 24
self.linerange = [(self.e[li, :, 1].max(), self.e[-li, :, 1].min()) for li in range(self.vbo.shape[0])]
self.showMaximized()
I am attempting to draw a speedometer using a Tkinter Canvas in Python and am having a few problems with my code that I can't seem to figure out. First off, here is what I have written:
import tkinter as tk
from tkinter import ttk
import math
class DrawMeter(tk.Canvas):
def __init__(self, parent, *args, **kwargs):
tk.Canvas.__init__(self, parent, *args, **kwargs)
self.config(bg = "grey")
if (int(self['height']) * 2 > int(self['width'])):
boxSide = int(self['width'])
else:
boxSide = int(self['height']) * 2
self.boxX = boxSide / 2
self.boxY = boxSide / 2
self.boxRadius = int(0.40 * float(boxSide))
self.start = 0
self.end = 1
self.drawBackground()
self.drawTicks()
self.drawNeedle()
def drawBackground(self):
bgColour = "black"
self.create_arc((self.boxX - self.boxRadius,
self.boxY - self.boxRadius,
self.boxX * 4,
self.boxY * 4),
fill = bgColour, start = 90)
def drawTicks(self):
length = self.boxRadius / 8
for deg in range(5, 85, 6):
rad = math.radians(deg)
self.Tick(rad, length)
for deg in range(5, 91, 18):
rad = math.radians(deg)
self.Tick(rad, length * 2)
def Tick(self, angle, length):
cos = math.cos(angle)
sin = math.sin(angle)
radius = self.boxRadius * 2
X = self.boxX * 2
Y = self.boxY * 2
self.create_line((X - radius * cos,
Y - radius * sin,
X - (radius - length) * cos,
Y - (radius - length) * sin),
fill = "white", width = 2)
def drawText(self, start = 0, end = 100):
interval = end / 5
value = start
length = self.boxRadius / 2
for deg in range(5, 91, 18):
rad = math.radians(deg)
cos = math.cos(rad)
sin = math.sin(rad)
radius = self.boxRadius * 2
self.create_text(self.boxX * 2 - (radius - length - 1) * cos,
self.boxY * 2 - (radius - length - 1) * sin,
text = str("{0:.1f}".format(value)),
fill = "white",
font = ("Arial", 12, "bold"))
value = value + interval
def setRange(self, start, end):
self.start = start
self.end = end
self.drawText(start, end)
def drawNeedle(self):
X = self.boxX * 2
Y = self.boxY * 2
length = self.boxRadius - (self.boxRadius / 4)
self.meterHand = self.create_line(X / 2, Y / 2, X + length, Y + length,
fill = "red", width = 4)
self.create_arc(X - 30, Y - 30, X + 30, Y + 30,
fill = "#c0c0c0", outline = "#c0c0c0", start = 90)
def updateNeedle(self, value):
length = self.boxRadius - (self.boxRadius / 4)
deg = 80 * (value - self.start) / self.end - 180
rad = math.radians(deg)
self.coords(self.meterHand, self.boxX * 2, self.boxY * 2,
self.boxX + length * math.cos(rad),
self.boxY + length * math.sin(rad))
value = 0
def update_frame():
global value
if value < 1:
value = value + 0.01
print(value)
meter.updateNeedle(value)
container.after(200, update_frame)
root = tk.Tk()
container = tk.Frame(root)
container.pack()
meter = DrawMeter(container, height = 200, width = 200, bg = "red")
meter.setRange(0, 1)
meter.pack()
update_frame()
root.mainloop()
So the problem is I am having is that my needle is drawing and updating properly on the screen. When I start the program, the needle starts at around 0.2ish, and goes until about 0.6 and then stop. I feel like my formula for calculating the needle's position is wrong, but I am not sure what about it is wrong.
The way I have it set up, is it takes the the percentage of the total the value is (value = self.start) / self.end) and multiplies it by 80 degrees (because my speedometer starts at the 5 degree marks and ends at 85) and them subtracts 180 so that the number makes the number go clockwise, not counter clockwise.
My placement of the canvas objects could also be off. My attempt was to set up a Speedometer that is a quarter circle at the bottom of the Canvas. However when you use the Canvas create_arc function, it draws the bottom right corner of your arc, in the center of the bounding box, meaning you make it the bottom right corner, I need to make by bounding box double the width and height of the canvas. I'm thinking maybe that threw me off a bit as well.
My problem was that I needed to create an offset value and add it to all four points in the Canvas.coords call.
I used OpenGL to draw about 20 circles. Each circle has 2 lines, ~10 segments, and all of them have different colors and lenght. FPS ~=4. How can I do this faster?
I am using Python 3 on Ubuntu
Code:
class ScreenOpenGL(Screen):
def __init__(self,layers,layers_lock):
""" Инициализирует экран и запускает его, потом всю программу """
Screen.__init__(self,"OpenGL",layers,layers_lock)
self.window = 0
self.quad = None
self.keypress = []
print("Fuck")
# self.infoScreen = ScreenCursesInfo()
self.infoScreen = ScreenStandartInfo()
GLUT.glutInit(sys.argv)
GLUT.glutInitDisplayMode(GLUT.GLUT_RGBA | GLUT.GLUT_DOUBLE | GLUT.GLUT_ALPHA | GLUT.GLUT_DEPTH)
GLUT.glutInitWindowSize(640, 480)
GLUT.glutInitWindowPosition(400, 400)
self.window = GLUT.glutCreateWindow(b"Project Evoluo alpha")
GLUT.glutDisplayFunc(self._loop) # Функция, отвечающая за рисование
GLUT.glutIdleFunc(self._loop) # При простое перерисовывать
GLUT.glutReshapeFunc(self._resize) # изменяет размеры окна
GLUT.glutKeyboardFunc(self._keyPressed) # Обрабатывает нажатия
self._initGL(640, 480)
field_params(640, 480)
print("Fuck")
def run(self):
# для threading
GLUT.glutMainLoop()
def _initGL(self,Width,Height):
GL.glShadeModel(GL.GL_SMOOTH);
GL.glClearColor(0.0, 0.0, 0.0, 0.0) # This Will Clear The Background Color To Black
GL.glClearDepth(1.0) # Enables Clearing Of The Depth Buffer
GL.glDepthFunc(GL.GL_LESS) # The Type Of Depth Test To Do
GL.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST)
GL.glEnable(GL.GL_BLEND); # Enable Blending
GL.glLineWidth(1.);
GL.glDisable(GL.GL_LINE_SMOOTH)
GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
self.width = Width
self.height = Height
self.quad = GLU.gluNewQuadric()
GLU.gluQuadricNormals(self.quad, GLU.GLU_SMOOTH)
GLU.gluQuadricTexture(self.quad, GL.GL_TRUE)
def _resize(self,Width,Height):
if Height == 0:
Height = 1
self.width = Width
self.height = Height
field_params(Width,Height)
GL.glViewport(0, 0, Width, Height) # Reset The Current Viewport And Perspective Transformation
GL.glMatrixMode(GL.GL_PROJECTION)
GL.glLoadIdentity()
GL.glOrtho(0.0,Width,0.0,Height,-1.0,1.0)
GL.glMatrixMode(GL.GL_MODELVIEW) # Select The Modelview Matrix
GL.glLoadIdentity()
def update(self):
GLUT.glutSwapBuffers()
def clear(self):
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
GL.glLoadIdentity() # Reset The View
def write(self,pos,str):
pass
def __del__(self):
del(self.infoScreen)
GLUT.glutDestroyWindow(self.window)
self._end()
# sys.exit()
def getch(self):
if self.keypress != []:
# print
return self.keypress.pop(-1)[0]
else:
return None
def _keyPressed(self,*args):
if args != None:
self.keypress.append(args)
print(self.keypress)
def _draw_prepare(self):
del(self._layers)
self._layers = []
for layer in layers:
if layer.__class__ == LayerObjects:
self._layers.append([layer.type,copy.deepcopy(layer.get_objs()),copy.copy(layer._attacked)])
def draw_eyes(self,vis,r_max,dphi):
for x in range(0,7):
GLU.gluPartialDisk(self.quad,
1,
vis[x][0] * r_max,
5,
3,
- ((x - 3) * 15 + 7.5 + dphi / pi * 180) + 90,
15)
def draw_sensitivity(self,sens,r_max,dphi):
for x in range(0,5):
GLU.gluPartialDisk(self.quad,
1,
sens[x] * r_max,
5,
3,
- (52.5 + (x+1) * 51 + dphi / pi * 180) + 90,
51)
pass
def draw_obj(self,obj,_id,pos,circle,lines,attacked):
# Кружок
GL.glLoadIdentity()
GL.glTranslatef(pos[0]-1,pos[1]-1,0)
red,green,blue = obj.color # берём цвет
GL.glColor3f(red,green,blue)
GLU.gluDisk(self.quad,*circle)
#Глазки
GL.glColor3f(1-red,1-green,1-blue)
try:
eyes = obj.eyes
except NameError:
pass
else:
self.draw_eyes(obj.eyes.eyes,obj.radius * k_screen,obj.pos[1])
# Прикосновения
GL.glColor3f(1,0,0)
try:
sensitivity = obj.sensitivity
except NameError:
pass
else:
self.draw_sensitivity(obj.sensitivity._sens,obj.radius * k_screen,obj.pos[1])
# Полосочки
GL.glBegin(GL.GL_LINES)
for color,x,y in lines:
GL.glColor3f(*color)
GL.glVertex3f(0,0,0)
GL.glVertex3f(x,y,0)
GL.glEnd()
def draw(self,layer):
global width,height
if layer[0] == "Layer Objects":
attacked = layer[2]
for obj in layer[1]:
#Стрелочки-направления
pos = [int(x) for x in obj.get_pos_screen()]
positions = [pos,]
radius_scr = obj.radius * k_screen
att = Vector(radius_scr * (1 +obj._attack_range*obj._attack),
obj.pos[1],
isPolar = True
)
speed = obj.speed[0] * k_screen * 5
if pos[0] < radius_scr:
positions.append([pos[0] + self.width,pos[1]])
if pos[0] + radius_scr > self.width:
positions.append([pos[0] - self.width,pos[1]])
if pos[1] < radius_scr:
positions.append([pos[0],pos[1] + self.height])
if pos[1] + radius_scr > self.height:
positions.append([pos[0],pos[1] - self.height])
for ps in positions:
self.draw_obj(obj,obj._id, ps , [0,obj.radius*k_screen,20,1], [ [ (1,0,0) , att.x, att.y ], [ (0,0,1) , speed.x, speed.y] ] , attacked)
self.infoScreen.draw()
Function code, that draws:
def _draw_prepare(self):
""" Копирует из глобальной переменной всё, что ей нужно """
self._layers = copy.deepcopy(layers)
def _loop(self):
global tick
if (self.ch == b'q') or (self.ch == b'\xb1') or (self.ch == 27) or (self.ch == 113):
isEnd = True
self.__del__()
del(self)
return 0
elif (self.ch == b's'):
self._is_draw = not self._is_draw
print("changed to %d" %self._is_draw)
else:
if (self.last_tick != tick) and (self._is_draw):
self.layers_lock.acquire(1)
self._draw_prepare()
self.layers_lock.release()
self.last_tick = tick
# рисует сцену
return self._main()
def _main(self):
global tick
self.clear()
if self._is_draw:
for layer in self._layers:
self.draw(layer)
self.update()
self.ch = self.getch()
And on GitHub