I need all mouse movement coordinates: as the mouse moves, I need a continuous sequence of (x,y) values. My current code leaves gaps:
def paint(self, event):
self.line_width = self.choose_size_button.get()
paint_color = 'white' if self.eraser_on else self.color
coord = (event.x, event.y)
if self.old_x == None or self.old_y == None:
else:
print(coord)
paint_color = self.color
self.c.create_line(self.old_x, self.old_y, event.x, event.y,
width=self.line_width, fill=paint_color,
capstyle=ROUND, smooth=TRUE, splinesteps=36)
coord is incomplete: it "jumps" some points, maybe because the mouse moves too fast and so it lost some points. How can I record all of the points in transit?
You can't record input with any finer grain than the sampling rate of the device. If you're already set up for fine-grained events on mouse movements, that's the best you can do in that respect.
However, if you need contiguous points for some reason, you can interpolate the missing points to make a connected path. The simple way is to connect the dots with Bresenham's line algorithm. I recommend that you use something to smooth the curve; there are several packages that will fit a nice curve to a sequence of points; look for the term "spline" as well.
Related
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.
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()
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()
I'm making a little platformer game using pygame, and decided that making a level editor for each level would be easier than typing each blocks' coordinate and size.
I'm using a set of lines, horizontally and vertically to make a grid to make plotting points easier.
Here's the code for my grid:
def makeGrid(surface, width, height, spacing):
for x in range(0, width, spacing):
pygame.draw.line(surface, BLACK, (x,0), (x, height))
for y in range(0, height, spacing):
pygame.draw.line(surface, BLACK, (0,y), (width, y))
I want the user's mouse to move at 10px intervals, to move to only the points of intersection. Here's what I tried to force the mouse to snap to the grid.
def snapToGrid(mousePos):
if 0 < mousePos[0] < DISPLAYWIDTH and 0 < mousePos[1] < 700:
pygame.mouse.set_pos(roundCoords(mousePos[0],mousePos[1]))
(BTW, roundCoords() returns the coordinates rounded to the nearest ten unit.)
(Also BTW, snapToGrid() is called inside the main game loop (while not done))
...but this happens, the mouse doesn't want to move anywhere else.
Any suggestions on how to fix this? If I need to, I can change the grid code too.
Thanks a bunch.
P.S. This is using the latest version of PyGame on 64 bit Python 2.7
First of all I think you're not far off.
I think the problem is that the code runs quite fast through each game loop, so your mouse doesn't have time to move far before being set to the position return by your function.
What I would have a look into is rather than to pygame.mouse.set_pos() just return the snapped coordinates to a variable and use this to blit a marker to the screen highlighting the intersection of interest (here I use a circle, but you could just blit the image of a mouse ;) ). And hide your actual mouse using pygame.mouse.set_visible(False):
def snapToGrid(mousePos):
if 0 < mousePos[0] < DISPLAYWIDTH and 0 < mousePos[1] < 700:
return roundCoords(mousePos[0],mousePos[1])
snap_coord = snapToGrid(mousePos)# save snapped coordinates to variable
pygame.draw.circle(Surface, color, snap_coord, radius, 0)# define the remaining arguments, Surface, color, radius as you need
pygame.mouse.set_visible(False)# hide the actual mouse pointer
I hope that works for you !
I'm looking for the easiest way to implement this. I'm trying to implement platforms (with full collision detection) that you can draw in via mouse. Right now I have a line drawing function that actually draws small circles, but they're so close together that they more or less look like a line. Would the best solution be to create little pygame.Rect objects at each circle? That's going to be a lot of rect objects. It's not an image so pixel perfect doesn't seem like an option?
def drawGradientLine(screen, index, start, end, width, color_mode):
#color values change based on index
cvar1 = max(0, min(255, 9 * index-256))
cvar2 = max(0, min(255, 9 * index))
#green(0,255,0), blue(0,0,255), red(255,0,0), yellow(255,255,0)
if color_mode == 'green':
color = (cvar1, cvar2, cvar1)
elif color_mode == 'blue':
color = (cvar1, cvar1, cvar2)
elif color_mode == 'red':
color = (cvar2, cvar1, cvar1)
elif color_mode == 'yellow':
color = (cvar2, cvar2, cvar1)
dx = end[0] - start[0]
dy = end[1] - start[1]
dist = max(abs(dx), abs(dy))
for i in xrange(dist):
x = int(start[0]+float(i)/dist*dx)
y = int(start[1]+float(i)/dist*dy)
pygame.draw.circle(screen, color, (x, y), width)
That's my drawing function. And here's my loop that I have put in my main game event loop.
i = 0
while (i < len(pointList)-1):
drawGradientLine(screen, i, pointList[i], pointList[i + 1], r, mode)
i += 1
Thanks for any help, collision detection is giving me a huge headache right now (still can't get it right for my tiles either..).
Any reason you want to stick with circles?
Rectangles will make the line/rectangle a lot more smooth and will make collision detecting a lot easier unless you want to look into pixel perfect collision.
You also don't seem to save your drawn objects anywhere (like in a list or spritegroup), so how are you going to check for collision?
Here's a leveleditor I did for game awhile back, it's not perfect, but it works:
https://gist.github.com/marcusmoller/bae9ea310999db8d8d95
How it works:
The whole game level is divided up into 10x10px grid for easier drawing
The leveleditor check if the mouse is being clicked and then saves that mouse position
The player now moves the mouse to another position and releases the mouse button, the leveleditor now saves that new position.
You now have two different coordinates and can easily make a rectangle out of them.
Instead of creating a whole bunch of rect objects to test collision against, I'm going to recommend creating something called a mask of the drawn-in collideable object, and test for collision against that. Basically, a mask is a map of which pixels are being used and which are not in an image. You can almost think of it as a shadow or silhouette of a surface.
When you call pygame.draw.circle, you are already passing in a surface. Right now you are drawing directly to the screen, which might not be as useful for what I'm suggesting. I would recommend creating a rect which covers the entire area of the line being drawn, and then creating a surface of that size, and then draw the line to this surface. My code will assume you already know the bounds of the line's points.
line_rect = pygame.Rect(leftmost, topmost, rightmost - leftmost, bottommost - topmost)
line_surf = pygame.Surface((line_rect.width, line_rect.height))
In your drawGradientLine function, you'll have to translate the point coordinates to the object space of the line_surf.
while (i < len(pointList)-1):
drawGradientLine(line_surf, (line_rect.x, line_rect.y), i, pointList[i], pointList[i+1], r, mode)
i += 1
def drawGradientLine(surf, offset, index, start, end, width, color_mode):
# the code leading up to where you draw the circle...
for i in xrange(dist):
x = int(start[0]+float(i)/dist*dx) - offset[0]
y = int(start[1]+float(i)/dist*dy) - offset[1]
pygame.draw.circle(surf, color, (x, y), width)
Now you'll have a surface with the drawn object blitted to it. Note that you might have to add some padding to the surface when you create it if the width of the lines you are drawing is greater than 1.
Now that you have the surface, you will want to create the mask of it.
surf_mask = pygame.mask.from_surface(line_surf)
Hopefully this isn't getting too complicated for you! Now you can either check each "active" point in the mask for collision within a rect from your player (or whatever other objects you want to collide withe drawn-in platforms), or you can create a mask from the surface of such a player object and use the pygame.Mask.overlap_area function to check for pixel-perfect collision.
# player_surf is a surface object I am imagining exists
# player_rect is a rect object I am imagining exists
overlap_count = surf_mask.overlap_area(player_surf, (line_rect.x - player_rect.x, line_rect.y - player_rect.y))
overlap_count should be a count of the number of pixels that are overlapping between the masks. If this is greater than zero, then you know there has been a collision.
Here is the documentation for pygame.Mask.overlap_area: http://www.pygame.org/docs/ref/mask.html#pygame.mask.Mask.overlap_area