Drawing convexity polygon in Pyglet - python

Is it possible to draw convexity polygon with Pyglet?
If yes, how can I draw it? I only know n vertex and their 2D coordinates.

You could use the primitive GL_POLYGON (or even try GL_LINE_LOOP).
Check documentation...
https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/api/pyglet/pyglet.graphics.html
http://pyglet.readthedocs.io/en/pyglet-1.3-maintenance/programming_guide/graphics.html
... and an example
Pyglet GL_QUADS and GL_POLYGON not working properly

Yes, cou can do it.
Just take the following code snippets to get a feeling for the functionality.
Play a little bit with the modes:
GL_POLYGON,
GL_TRIANGLES,
GL_TRIANGLE_FAN and
GL_TRIANGLE_STRIP.
Left mouse click button increases points in window. First drawing is shown after clicking three times. Del-Button clears the window.
GL_POLYGON and GL_TRIANGLE_FAN behave in the same way, the first point is the anchor point of the convex polygon and is used for every drawn triangle from the points list.
GL_TRIANGLE takes 3 points for drawing a triangle, then the next 3 points and so on.
With GL_TRIANGLE_STRIP you can draw every complex structure. But there you have be careful for the given points. Sometimes, you have to visit a point more than one time.
The first triangle is drawn by points 1-3.
The second triangle is drawn by points 2-4, then 3-5, 4-6 and so on.
import pyglet
from pyglet.window import key
global n
global vertices
global colors
window = pyglet.window.Window()
n = 0
vertices = []
colors = []
polygon = None
main_Batch = pyglet.graphics.Batch()
#window.event
def on_draw():
window.clear()
main_Batch = pyglet.graphics.Batch()
if n > 2:
polygon = main_Batch.add(n, pyglet.gl.GL_POLYGON, None,
('v2i', vertices),
('c3B', colors))
main_Batch.draw()
#window.event
def on_key_press(symbol, modifiers):
if symbol == key.DELETE:
global n;
global vertices
global colors
vertices = []
colors = []
n = 0
#window.event
def on_mouse_press(x, y, button, modifiers):
if button == pyglet.window.mouse.LEFT:
global n
vertices.append(x)
vertices.append(y)
n = n + 1
colors.append(255)
colors.append(255)
colors.append(255)
pyglet.app.run()

Related

Zoomable plot of a list of colors in python with different coordinate systems

I'm trying to program a Python GUI for about 2 - 3 days now, which allows to zoom into a color palette. Since in the GUI or in the window the coordinates start with 0 and get bigger and bigger on x and y, a translation of the points has to be done. Example: I have a list "palette" of 12 colors in the format: [rgb1, rgb2, rgb3 ... ] and would like to draw it on the window. I prefer to draw the color strip from left to right using a for loop, and each iteration it draws a line from bottom to top.
so that each pixel does not have a different color, i would have to divide the loop variable by the range of the window and multiply it by the range of the palette. Then I would have to get the color from the list using the rounded value. If I then take this value with a variable (the zoom), I can zoom in and out of the color strip. But then the center of the zoom is always to the left of the window (and he first color) and not where my mouse is.
To put it briefly:
How can I program something like this: https://www.geeksforgeeks.org/how-to-zoom-in-on-a-point-using-scale-and-translate/, if I don't want to draw a single square at x1, y1, x2, y2, but a loop that iterates over the canvas?
NOTE: i need this only for the x axis, the y axis is fixed.
Any help and formula (note that i want to use a render loop) is welcome!
PS: here is my "mathematically incorrect" code (Python and pyqt6):
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
palette = [QColor(0,0,0),QColor(100,0,0),QColor(0,100,0),QColor(0,0,100),QColor(100,0,100),QColor(255,0,0)]
Range = self.width()-20
Len = len(palette)
for k in range(Range):
P = (((k-self.P)*self.zoom)+self.P)/Range*Len
if P >= 0 and P < Len:
qp.setPen(palette[math.floor(P)])
qp.drawLine(10+k,0,10+k,self.width())
def wheelEvent(self, e):
if e.angleDelta().y() < 0:
self.zoom = self.zoom/1.25
else:
self.zoom = self.zoom*1.25
self.P -= (e.position().x() + self.P)/self.zoom
print(self.P)
self.update()
self.P = e.position().x()-self.width()/2
I tried to use some formulas from the internet but these were different (not meant for iterating)
Additionally, I tried to create my own formula, but either I didn't see where the color palette flew to, or it didn't zoom in the direction of my mouse....
It should look like a zoom function in an image editing program.

Pyglet on_key_press and on_mouse_motion are being very inconsistent

Background
I've been toying around with pyglet when I stumbled across this guy's Minecraft clone. Github is here: https://github.com/fogleman/Minecraft
I've made some modifications (for Python 3 and some of my own preferences), and the complete code is here: modified minecraft.
The Problem
Whenver I run the code, it may sometimes not register any mouse movements or key presses. It is rare, but it can happen occasionally. I would say that out of 10 times, it'll break once.
Details
I don't even know what the culprit is, but I'll provide some snippets code.
It's unpredictable, but there are some ways to fix it. The only sure-fire way currently is to FORCE QUIT (not just quit) the application and then restart it.
I'm not sure why, and I've tried all sorts of things to try and fix it.
If it matters, I'm using macOS Mojave, Python 3.8.2, and Pyglet 1.5.14
Here's the __init__ function for the window:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Whether or not the window exclusively captures the mouse.
self.exclusive = False
# When flying gravity has no effect and speed is increased.
self.flying = False
# Strafing is moving lateral to the direction you are facing,
# e.g. moving to the left or right while continuing to face forward.
#
# First element is -1 when moving forward, 1 when moving back, and 0
# otherwise. The second element is -1 when moving left, 1 when moving
# right, and 0 otherwise.
self.strafe = [0, 0]
# Current (x, y, z) position in the world, specified with floats. Note
# that, perhaps unlike in math class, the y-axis is the vertical axis.
self.position = (0, 0, 0)
# First element is rotation of the player in the x-z plane (ground
# plane) measured from the z-axis down. The second is the rotation
# angle from the ground plane up. Rotation is in degrees.
#
# The vertical plane rotation ranges from -90 (looking straight down) to
# 90 (looking straight up). The horizontal rotation range is unbounded.
self.rotation = (0, 0)
# Which sector the player is currently in.
self.sector = None
# The crosshairs at the center of the screen.
self.reticle = None
# Velocity in the y (upward) direction.
self.dy = 0
# A list of blocks the player can place. Hit num keys to cycle.
self.inventory = [BRICK, GRASS, SAND]
# The current block the user can place. Hit num keys to cycle.
self.block = self.inventory[0]
# Convenience list of num keys.
self.num_keys = [
key._1, key._2, key._3, key._4, key._5,
key._6, key._7, key._8, key._9, key._0]
# Instance of the model that handles the world.
self.model = Model()
# The label that is displayed in the top left of the canvas.
self.label = pyglet.text.Label('', font_name='Arial', font_size=18,
x=10, y=self.height - 10, anchor_x='left', anchor_y='top',
color=(0, 0, 0, 255))
# This call schedules the `update()` method to be called
# TICKS_PER_SEC. This is the main game event loop.
pyglet.clock.schedule_interval(self.update, 1.0 / TICKS_PER_SEC)
Here's the input handlers:
def on_mouse_press(self, x, y, button, modifiers):
""" Called when a mouse button is pressed. See pyglet docs for button
amd modifier mappings.
Parameters
----------
x, y : int
The coordinates of the mouse click. Always center of the screen if
the mouse is captured.
button : int
Number representing mouse button that was clicked. 1 = left button,
4 = right button.
modifiers : int
Number representing any modifying keys that were pressed when the
mouse button was clicked.
"""
if self.exclusive:
vector = self.get_sight_vector()
block, previous = self.model.hit_test(self.position, vector)
if (button == mouse.RIGHT) or \
((button == mouse.LEFT) and (modifiers & key.MOD_CTRL)):
# ON OSX, control + left click = right click.
if previous:
self.model.add_block(previous, self.block)
if button == pyglet.window.mouse.LEFT and block:
texture = self.model.world[block]
self.model.remove_block(block)
else:
self.set_exclusive_mouse(True)
def on_mouse_motion(self, x, y, dx, dy):
""" Called when the player moves the mouse.
Parameters
----------
x, y : int
The coordinates of the mouse click. Always center of the screen if
the mouse is captured.
dx, dy : float
The movement of the mouse.
"""
if self.exclusive:
m = 0.15
x, y = self.rotation
x, y = x + dx * m, y + dy * m
y = max(-90, min(90, y))
self.rotation = (x, y)
def on_key_press(self, symbol, modifiers):
if symbol == key.W:
self.strafe[0] -= 1
if symbol == key.S:
self.strafe[0] += 1
if symbol == key.A:
self.strafe[1] -= 1
if symbol == key.D:
self.strafe[1] += 1
if symbol == key.SPACE:
if self.dy == 0:
self.dy = JUMP_SPEED
if symbol == key.ESCAPE:
self.set_exclusive_mouse(False)
if symbol == key.TAB:
self.flying = not self.flying
if symbol in self.num_keys:
index = (symbol - self.num_keys[0]) % len(self.inventory)
self.block = self.inventory[index]
And finally, here's the setup:
""" Configure the OpenGL fog properties.
"""
# Enable fog. Fog "blends a fog color with each rasterized pixel fragment's
# post-texturing color."
glEnable(GL_FOG)
# Set the fog color.
glFogfv(GL_FOG_COLOR, (GLfloat * 4)(0.5, 0.69, 1.0, 1))
# Say we have no preference between rendering speed and quality.
glHint(GL_FOG_HINT, GL_DONT_CARE)
# Specify the equation used to compute the blending factor.
glFogi(GL_FOG_MODE, GL_LINEAR)
# How close and far away fog starts and ends. The closer the start and end,
# the denser the fog in the fog range.
glFogf(GL_FOG_START, 50.0)
glFogf(GL_FOG_END, 100.0)
def setup():
""" Basic OpenGL configuration.
"""
# Set the color of "clear", i.e. the sky, in rgba.
glClearColor(0.5, 0.69, 1.0, 1)
# Enable culling (not rendering) of back-facing facets -- facets that aren't
# visible to you.
glEnable(GL_CULL_FACE)
# Set the texture minification/magnification function to GL_NEAREST (nearest
# in Manhattan distance) to the specified texture coordinates. GL_NEAREST
# "is generally faster than GL_LINEAR, but it can produce textured images
# with sharper edges because the transition between texture elements is not
# as smooth."
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
setup_fog()
def main():
window = Window(width=800, height=600, caption='Minecraft', resizable=True)
# Hide the mouse cursor and prevent the mouse from leaving the window.
setup()
if __name__ == '__main__':
main()
pyglet.app.run()
Here's texture.png:
Here's an example of what's happening (the black circles means I've clicked the mouse, and at the end, I was rapidly pressing W):
gif
What I've Done
Here is what I've done so far:
Looked at the Pyglet Docs
Researched on google with various phrasing and keywords
It sounds like it could be a Mac related issue. Here is a bug report on something similar happening with Mac: https://github.com/pyglet/pyglet/issues/225
One thing I would try is just try a barebones setup and see if the problems persists with the minimal code. If this still occurs, there is most likely a bug in the Mac/Pyglet interaction. If the basic sample works, there might be a bug in the Minecraft example.
import pyglet
window = pyglet.window.Window()
#window.event
def on_draw():
print('on_draw')
window.clear()
#window.event
def on_mouse_motion(x, y, dx, dy):
print('on_mouse_motion', x, y, dx, dy)
#window.event
def on_key_press(symbol, modifiers):
print('on_key_press', symbol, modifiers)
pyglet.app.run()

Pyglet on_draw wont draw updated function

The crtaj() function (the main drawing function) takes two global matrices, "T" and "P", applies transformations on the global vertices "vrhovi" using the two matrices and stores the transformed vertices into "novivrhovi", and then draws the polygon using the transformed vertices "novivrhovi" and global "poligoni" (describes the connections of vertices). When the key "up" is pressed, the matrices "T" and "P" are updated. What i want, is to draw on every one of these updates, but after the initial draw the screen goes blank after first "up" is pressed. I am 100% certain my transformations are okay, because pressing the key up once gives matrices for which i have tried to set to be initial , and it draws it correctly so no reason not to draw correctly when using default initial and pressing up, because the result is the same and the function even prints correct vertices every time I press "up" but just doesn't show anything.
#window.event
def on_draw():
glScalef(150,150,150)
glTranslatef(sum(xkord)/2,sum(ykord)/2,0)
window.clear()
crtaj()
#window.event
def on_key_press(key, modifiers):
global vrhovi
if (key == pyglet.window.key.UP):
ociste[0][0]=ociste[0][0]+1
transform()
on_draw()
#main draw
def crtaj():
glBegin(GL_LINE_LOOP)
global T
global P
global vrhovi
global poligoni
#calculate new vertices of polygon-->novivrhovi
novivrhovi = []
for vrh in vrhovi:
novivrh = vrh.copy()
novivrh.append(1)
novivrh = np.matrix(novivrh)
novivrh = novivrh.dot(T)
novivrh = novivrh.dot(P)
novivrh = novivrh.tolist()
novivrhovi.append(novivrh[0])
print("N O V I V R H O V I")
print(novivrhovi)
#draw the poligon
for poligon in poligoni:
index0 = poligon[0]-1
index1 = poligon[1]-1
index2 = poligon[2]-1
glVertex4f(novivrhovi[index0][0],novivrhovi[index0][1],novivrhovi[index0][2],novivrhovi[index0][3])
glVertex4f(novivrhovi[index1][0],novivrhovi[index1][1],novivrhovi[index1][2],novivrhovi[index1][3])
glVertex4f(novivrhovi[index2][0],novivrhovi[index2][1],novivrhovi[index2][2],novivrhovi[index2][3])
glEnd()
pyglet.app.run()
but after the initial draw the screen goes blank after first "up" is pressed.
The Legacy OpenGL matrix operations like glScalef and glTranslatef do not just set a matrix, they define a new matrix and multiply the current matrix by the new matrix.
OpenGL is a state engine, states are kept until they are changed again, even beyond frames. Hence in your application the current matrix is progressively scaled and translated.
Load the Identity matrix at the begin of on_draw:
#window.event
def on_draw():
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glScalef(150,150,150)
glTranslatef(sum(xkord)/2,sum(ykord)/2,0)
window.clear()
crtaj()
PyGlet sets by default an Orthographic projection projection matrix to window space. See pyglet.window. If you want a different projection, it can be set by glOrtho:
#window.event
def on_draw():
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, window.width, window.height, 0, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glScalef(150,150,150)
glTranslatef(sum(xkord)/2,sum(ykord)/2,0)
window.clear()
crtaj()

Simulation using python and graphics.py image

I am trying to create a simulator. (referring to John Zelle's graphics.py)
Basically, my object will make use of graphics.py to display the object as a circle. Then, using the .move method in the class in graphics.py, the object will move in the x direction and y direction. If the object is currently drawn, the circle is adjusted to the new position.
Moving just one object can easily be done with the following codes:
win = GraphWin("My Circle", 100, 100)
c = Circle(Point(50,50), 10)
c.draw(win)
for i in range(40):
c.move(30, 0) #speed=30
time.sleep(1)
win.close()
However, I want the program to display multiple circles at once that moves at different speed. I've created a Circle object class which takes speed as an input, and a list with 3 Circle objects in it
circle = []
circle1 = Car(40)
circle2= Car(50)
circle3 = Car(60)
In summary, my question is, how do make use of this list such that I am able to display and move multiple circles in one window at once using the methods available in graphics.py?
That all depends on how you create your Car class, but nothing stops you from using the same code to move multiple circles in the same refresh cycle, e.g.:
win = GraphWin("My Circle", 1024, 400)
speeds = [40, 50, 60] # we'll create a circle for each 'speed'
circles = [] # hold our circles
for speed in speeds:
c = Circle(Point(50, speed), 10) # use speed as y position, too
c.draw(win) # add it to the window
circles.append((c, speed)) # add it to our circle list as (circle, speed) pair
for i in range(40): # main animation loop
for circle in circles: # loop through the circles list
circle[0].move(circle[1], 0) # move the circle on the x axis by the defined speed
time.sleep(1) # wait a second...
win.close()
Of course, if you're already going to use classes, you might as well implement move() in it so your Car instances can remember their speed and then just apply it when you call move() on them in a loop.

Create a line using a list of coordinates in python GUI

I've been trying to create a graph using a create_line and a list of (x,y) points.
import Tkinter
Screen = [a list of screen coordinates]
World = []
for x,y in screen:
World.append(somefunctiontochange(x,y))
if len(World) >= 2:
Canvas.create_line(World)
The line doesn't show in my canvas though, and no error was given. Any help?
Took me a while but this is how you draw to a canvas in the way you want:
import Tkinter as tk
root = tk.Tk()
root.geometry("500x500")
root.title("Drawing lines to a canvas")
cv = tk.Canvas(root,height="500",width="500",bg="white")
cv.pack()
def linemaker(screen_points):
""" Function to take list of points and make them into lines
"""
is_first = True
# Set up some variables to hold x,y coods
x0 = y0 = 0
# Grab each pair of points from the input list
for (x,y) in screen_points:
# If its the first point in a set, set x0,y0 to the values
if is_first:
x0 = x
y0 = y
is_first = False
else:
# If its not the fist point yeild previous pair and current pair
yield x0,y0,x,y
# Set current x,y to start coords of next line
x0,y0 = x,y
list_of_screen_coods = [(50,250),(150,100),(250,250),(350,100)]
for (x0,y0,x1,y1) in linemaker(list_of_screen_coods):
cv.create_line(x0,y0,x1,y1, width=1,fill="red")
root.mainloop()
You need to supply create_line with the x,y positions at the start and end point of the line, in the example code above (works) I'm drawing four lines connecting points (50,250),(150,100),(250,250),(350,100) in a zigzag line
Its worth pointing out also that the x,y coords on a canvas start at the top left rather than the bottom left, think of it less like a graph with the x,y = 0,0 in the bottom left of the canvas and more how you would print to a page starting in top left corner moving to the right in the x and with the y incrementing as you move down the page.
I used:
http://www.tutorialspoint.com/python/tk_canvas.htm as reference.
If you aren't getting errors and you're certain your function is being called, you probably have one of three problems:
Is your canvas visible? A common mistake for beginners is to either forget to pack/grid/place the canvas, or to neglect to do that for all of its containers. An easy way to verify is to temporarily give your canvas a really bright background so that it stands out from the rest of the GUI.
Have you set the scroll region? The other explanation is that the drawing is happening, but it's happening in an area that is outside the viewable portion of the canvas. You should be setting the scrollregion attribute of the canvas after creating your drawings, to make sure everything you're drawing can be made visible.
Does your canvas and canvas objects have an appropriate color? It's possible that you've changed the background of the canvas to black (since you don't show that code in your question), and you're using a default color of black when creating your lines.

Categories