Pyglet. How to change picture(animate) for vertices dynamically. OpenGL - python

Environment:
Python: 3.6.6
pyglet version: 1.3.2
Code base:
abstract_model.py
import pyglet
def get_texture_group(file, order_group_index):
texture = pyglet.image.load(file).texture
order_group = pyglet.graphics.OrderedGroup(order_group_index)
return pyglet.graphics.TextureGroup(texture, order_group)
class AbstractModel(object):
def _create_as_vertex(self):
v_x = self.cell_data.get("x") * 32
v_y = self.cell_data.get("y") * -1 * 32
texture_group = self.map_type_iamge.get(self.cell_data.get("t"))
x_offset = self.x_offset * self.scale
x, y, z = v_x + x_offset, v_y, self.z
x_ = (texture_group.texture.width * self.scale + x_offset + v_x)
y_ = (texture_group.texture.height * self.scale + v_y)
tex_coords = ('t2f', (0, 0, 1, 0, 1, 1, 0, 1))
self.vertices = self.batch.add(
4, pyglet.gl.GL_QUADS,
texture_group,
('v3f', (x, y, z,
x_, y, z,
x_, y_, z,
x, y_, z)),
tex_coords)
def _animate(self, dt):
# lets assume that I have list of pyglet.graphics.TextureGroup
# and they should somehow be drawn one after other
print("I need change image. dt=", dt, self)
pyglet.clock.schedule_once(self._animate, 1)
ground3d.py
import os
import pyglet
import settings
from models import abstract_model
GROUND_DIR = os.path.join(settings.STATIC_DIR, "ground")
order_group_index = 0
map_type_iamge = {
1: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "w1.png"), order_group_index),
2: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "t1.png"), order_group_index),
1001: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "t1_direction.png"), order_group_index),
}
class Ground3D(abstract_model.AbstractModel):
def __init__(self, cell_data, batch):
self.batch = batch
self.cell_data = cell_data
self.map_type_iamge = map_type_iamge
self.scale = 1
self.x_offset = 0
self.z = 0
self.entity = None
self._create_as_vertex()
pyglet.clock.schedule_once(self._animate, 1)
Explanation:
I have models(just flat rect for an example) which should be placed on 3 dimensions. And these models should be animated, like picture_1, after second picture_2, ... etc.
As I understood from my previous question using pyglet.sprite.Sprite() in 3D batch is not a good idea.
Question:
How I can change pictures(using TextureGroup or any other approaches) on self.vertices?
Or which arroach/classes I use use to implement it. I can't find any examples for such (as for my simple vision) usual case as animation for some flat models in 3 dimensions.
There are many example about rotating/moving/resizing of vertices, but how to build a correct question(is animation aspect) for getting answer in google - I don't know.
PS: If you, reader, have any usefull links on this subject(for pyglet or just for OpenGL) I would be very appreciated you share this link(s) in comment.

Texture coordinates.
You should have a single texture atlas for all frames of all different things that are animated ever.
Preferably, everything should have same animation speed and same amount of frames all the time.
Let's say there's two sprites that have 2 frames for entire animation, and they are stored in 64x64 texture atlas. (EDIT: sorry for ambiguity, 64x64 PIXELS, just because it could imply that we have 64x64 tile atlas, same everywhere else where I mention this)
Now, you need to have a global timer with global value which indicates current animation frame, not game frame. It should be independent of framerate.
Said value should be updated every once in a while at your desired speed like this:
current_frame = (current_frame + 1) % animation_length
Since we have 2 frames in this example, it will turn out like this:
# init
animation_length = 2
current_frame = 0
# updates:
current_frame = (0 + 1) % 2 # 1 % 2 -> 1
current_frame = (1 + 1) % 2 # 2 % 2 -> 0
...
Now, you need to update UV's of all your sprites only when the frame changes.
UV's start from left right and go from 0 to 1 (as far as I remember, for the sake of this example, they do, shhh).
Since we have 2 frames each, we can calculate "tiles" in the UV coordinates like this:
tile_width = 1.0 / frames # 2 frames each, width will be 0.5
tile_height = 1.0 / sprites # 2 sprites each, height will be 0.5 too, perfect
Now, on first frame, you generate your UV's like normal, you just take vertical ID or something, and use tile_height * sprite_id to get current V coordinate, and your U is calculated like tile_width * current_frame.
This assumes that you already have sprite batching so what you do is go over every sprite on update, and basically just recalculate new UV's with new frame, meaning all sprites change their frame to the next one, yay!
If you want to have systems that are independent of eachother, say, very slow animations for some, and faster for others, you'll need different sprite batches or proper calculation on from where to where you need to update UV's in vertex buffer array. Everything else is exactly the same, except now current_frame won't be global but rather contained, preferebly in some list or separate object that manages timers for animations.
You don't need to change anything in your shaders, they just need right UV's for the frames and you're set.
By the way, this is very basic, you could apply some logic yourself so you could instead have 16x16 grid of 32x32 pixels in your texture, each line of sprites having 4 different animations, these could be either sprite's states (attack, run, etc), but how you do it is entirely on you, most importantly, get it to work. Goodluck.
But if you do it the way I said, then state will be another integer, and UV for state, assuming all states have exactly the same width, it would be like this:
state_width = 1 / states
tile_width = 1 / (states * frames_per_state)
U = state_width * current_state + tile_width * current_frame
Now, one issue arises, player could start his animation at the last attack frame.
It's normal, entities with actions should all have individual timers, what I described above, is for tons of sprites that are just background, like grass. Now when you divided it up, you could have a proper way to reset current frame to 0 when new state is assigned.
If your entities are objects, you could write proper methods that recalculate UV's every time you rebuild the sprite batch using those sprites, and then timers itselves could be contained in objects.
We need to draw something? Check animation state, has it changed, no? Send UV's that were calculated before, otherwise, wait a bit, we need to recalculate, only then add those to VBO, and well, render your thing, in the end, it will appear as if you have animations, even though really, it's just a simple, but great UV manipulation.
Goodluck.

Related

Moving the cursor in relation to a certain fix-point in Python

is there any possibility to move the cursor in Python with % starting from a certain coordinate like (1,1)?
I am using pyautogui atm to automate and I thought it would be quite convenient if this is independent from the monitor size making it universal.
Thanks for your help in advance!
It's possible indirectly. As detailed at the top of the Mouse Control Functions page of the documentation, you can get the screen size using the size() function (which returns a tuple of (X, Y)). You can then do the math to figure out how many screen pixels equal the percentage you're looking for, then call moveTo() to go there.
# script to move mouse 50% to the right and down
import pyautogui as pag
percentage = 0.5
cur_X, cur_Y = pag.position() # current X and Y coordinates
size_X, size_Y = pag.size() # screen size
goto_X = (size_X - cur_X) * percentage + cur_X # current location plus half
goto_Y = (size_Y - cur_Y) * percentage + cur_Y # the distance to the edge
pag.moveTo(goto_X, goto_Y, 1) # move to new position, taking 1 second

In the memory puzzle game on pygame, can I keep the original reveal speed to be slow and then make it faster when playing the game?

This is the game code
https://inventwithpython.com/pygame/chapter3.html
I currently have the reveal speed set at 1, however this is for the initial reveal and the reveals while playing the game. Is there anyway that I am able to keep that initial reveal speed at 1, but then change the reveal speed while playing to something quicker?
In order to do this I feel that I would need to add a line of code under the revealspeed on line 12, I just don't know what the new line should say.
FPS = 30 # frames per second, the general speed of the program
WINDOWWIDTH = 640 # size of window's width in pixels
WINDOWHEIGHT = 480 # size of windows' height in pixels
REVEALSPEED = 2 # speed boxes' sliding reveals and covers
BOXSIZE = 60 # size of box height & width in pixels
GAPSIZE = 10 # size of gap between boxes in pixels
BOARDWIDTH = 8 # number of columns of icons
BOARDHEIGHT = 7 # number of rows of icons
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
So we want to do the animation with two different speeds, so let's create a new global variable for that first:
...
WINDOWHEIGHT = 480 # size of windows' height in pixels
# ADD THIS
INITIALREVEALSPEED = 1 # speed boxes' sliding reveals and covers AT THE START OF THE GAME
REVEALSPEED = 8 # speed boxes' sliding reveals and covers
BOXSIZE = 40 # size of box height & width in pixels
...
By searching for REVEALSPEED we see that the animation is handled in the revealBoxesAnimation and coverBoxesAnimation functions. They use the REVEALSPEED constant (not really constant, but hey), but we want the speed to be dynamic, so let's just pass the speed we want to use as a parameter. Change the functions to:
def revealBoxesAnimation(board, boxesToReveal, speed=REVEALSPEED):
# Do the "box reveal" animation.
for coverage in range(BOXSIZE, (-speed) - 1, -speed):
drawBoxCovers(board, boxesToReveal, coverage)
def coverBoxesAnimation(board, boxesToCover, speed=REVEALSPEED):
# Do the "box cover" animation.
for coverage in range(0, BOXSIZE + speed, speed):
drawBoxCovers(board, boxesToCover, coverage)
We still use REVEALSPEED as a default value, so we don't have to change each method call.
Since we want to only slow down the animation at the start of the game, we only have to change the method invocations that happens at the start. If we search for places where revealBoxesAnimation is used, we find the startGameAnimation function. Let's change it to:
def startGameAnimation(board):
# Randomly reveal the boxes 8 at a time.
coveredBoxes = generateRevealedBoxesData(False)
boxes = []
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
boxes.append( (x, y) )
random.shuffle(boxes)
boxGroups = splitIntoGroupsOf(8, boxes)
drawBoard(board, coveredBoxes)
for boxGroup in boxGroups:
revealBoxesAnimation(board, boxGroup, INITIALREVEALSPEED)
coverBoxesAnimation(board, boxGroup, INITIALREVEALSPEED)
And that's it.

Equations for walking forward

I wrote a simple 3D engine for pygame. Now I'm trying to create a simple first person controller for moving around a scene. The engine assumes a 3D grid with the player starting facing the negative z axis. The camera can rotate about the three axis as if it was at the origin. Currently the first person controller can only rotate around the y axis - i.e. look around 360 degrees. I'm trying to make the wasd controls move relative to which direction the camera is looking at.
Here is what I currently have (self.camera is a camera object from my 3D engine, which has the attributes x, y, z (position) and x_rot, y_rot, z_rot (rotation):
import python3d as p3d
import pygame
import math
class first_person_controller:
def __init__(self,scene,camera,end_func):
pygame.event.set_grab(True)
pygame.mouse.set_visible(False)
self.scene = scene
self.camera = camera
self.end_func = end_func
def fps_control(self):
keys = pygame.key.get_pressed()
relative = pygame.mouse.get_rel()
self.camera.change_camera_rot([0,relative[0]/1000*-1,0])
if keys[pygame.K_w]:
self.camera.z += math.cos(self.camera.y_rot) * 5 * -1
self.camera.x += math.sin(self.camera.y_rot) * 5 * -1
if keys[pygame.K_s]:
self.camera.z += math.cos(self.camera.y_rot) * 5
self.camera.x += math.sin(self.camera.y_rot) * 5
if keys[pygame.K_a]:
self.camera.z += math.cos(self.camera.y_rot + math.radians(90)) * 5 * -1
self.camera.x += math.sin(self.camera.y_rot + math.radians(90)) * 5 * -1
if keys[pygame.K_d]:
self.camera.z += math.cos(self.camera.y_rot + math.radians(90)) * 5
self.camera.x += math.sin(self.camera.y_rot + math.radians(90)) * 5
if keys[pygame.K_ESCAPE]:
self.end_func()
if keys[pygame.K_c]:
self.camera.y = 0
elif not keys[pygame.K_c]:
self.camera.y = 5
This is supposed to fit into a normal pygame loop like:
while True:
clear screen
fps_control()
render scene
However, when implemented the first person controller goes off course, and at least appears to not walk in a straight line. I can't tell if this is because of my math, or my engine.
If someone could tell me which is the issue - the trigonometry or the 3D engine, and if its the trigonometry suggest better equations, that would be great.
If it helps, here is the 3D engine, but as of right now, the code isn't very readable.
Thanks.
I'll probably be voted down for it, but whatever.
Your code reminds me a lot an earlier attempt to a 3D engine of my own. However, after facing numerous problem, I figured that Vectors would help me a LOT, and then I learned Matrices, as these helped TONS MORE.
And here is a link towards a course named Vector Math for 3D computer graphics which is already great, but then you should even go a step further by using 4x4 Matrices. May seems like boring stuff, but you'll be glad to see how easy the logic becomes once you get it.

Tkinter canvas updating speed reduces during the course of a program

The following python program creates a Tkinter Canvas object and draws random matrix on it.
It also measures the time it takes to make 10 subsequent updates. As you may see from the
output below, this time grows continiously and substantially during the course of
the program. What is the reason for this behavior and how can I fix it?
from Tkinter import Tk, Canvas
import time
import numpy as np
window = Tk()
nRows = 30
nCols = 30
CELL_SIZE = 10
canvas = Canvas(window, width=CELL_SIZE*nRows,
height=CELL_SIZE*nCols)
canvas.pack()
def drawbox(m):
for y in range(nRows):
for x in range(nCols):
if m[y][x]:
color = '#00FF00'
else:
color = '#000000'
canvas.create_rectangle(CELL_SIZE*x,
CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE,
CELL_SIZE*y+CELL_SIZE,
fill=color,
outline="#000000", width=1)
count = 0
timeStart = time.time()
while(True):
board = np.random.rand(nRows, nCols) > 0.5
if count % 10 == 0:
print '%.1f seconds'%(time.time() - timeStart)
timeStart = time.time()
count = 0
count += 1
drawbox(board)
canvas.after(5)
canvas.update()
Here is the output
0.0 seconds
1.7 seconds
4.1 seconds
6.3 seconds
8.7 seconds
You create new items at each updates. The canvas display all the rectangles you have previously added and thus go slower and slower (each update create 900 rectangles, after 30 you have 27,000 objects in your scene...)
To avoid this, you may create your rectangles once, and then only update their colors.
You could have at toplevel:
rectangles = [ [ canvas.create_rectangle (CELL_SIZE*x, CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE, CELL_SIZE*y+CELL_SIZE,
fill="#000000",outline="#000000", width=1)
for x in range(nCols)] for y in range(nRows)]
and in drawbox:
canvas.itemconfig(rectangles[y][x], fill=color)
Every time drawbox is called in your program, you're creating a new set of rectangles and then drawing them on top of the old rectangles. As time goes on, you're drawing more and more rectangles (even though it doesn't look like it since the new rectangles are being drawn above the old ones). Also note that with the way your program is written, you're bleeding memory.
The way to fix this is to create the rectangles on the first go-around and then update them on the subsequent passes using canvas.itemconfig(rectangle_id,fill=color). I've posted an (ugly) modification to your drawbox below which accomplishes this.
def drawbox(m,_rectangles={}):
if(_rectangles):
myrectangles=_rectangles
else:
myrectangles={}
for y in range(nRows):
for x in range(nCols):
if m[y][x]:
color = '#00FF00'
else:
color = '#000000'
if(not _rectangles):
cid=canvas.create_rectangle(CELL_SIZE*x,
CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE,
CELL_SIZE*y+CELL_SIZE,
fill=color,
outline="#000000", width=1)
myrectangles[(y,x)]=cid
else:
canvas.itemconfig(_rectangles[(y,x)],fill=color)
if(not _rectangles):
_rectangles.update(myrectangles)
The canvas is known to be slow the more items you add (though it can typcially handle 1000's or 10's of 1000's without too much problem). You have a couple of problems. One, as other answers have pointed out, is that you keep creating more and more objects. By reusing the existing objects and updating their coordinates and colors you should see a dramatic improvement in speed.
The second problem is your infinite loop and your sleep (canvas.after(5)). There's a much better way to achieve the effect without the annoying side effect of your GUI freezing for 5 ms at a time.
All you need to do is create a function that draws or updates the objects, then puts an event on the queue to call itself again after some period of time. It will then automatically update without you having to explicitly create a loop.
For example:
def redraw():
board = np.random.rand(nRows, nCols) > 0.5
drawbox(board)
canvas.after(100, redraw)

Maintaining view/scroll position in QGraphicsView when swapping images

I'm having trouble with zooming TIFF images loaded into a QGraphicsView with QGraphicsPixmapItem.
The problem is more maintaining image quality along with having a zoom speed that doesn't make the application choke. To begin with I was just replacing the image with a scaled QPixmap - I used Qt.FastTransformation while the user was holding down a horizontal slider and then when the slider was released replaced the pixmap again with another scaled pixmap using Qt.SmoothTransformation. This gave a nice quality zoomed image but the zooming was jerky after the image size started to increase to larger than its original size; zooming out of the image was fine.
Using QTransform.fromScale() on the QGraphicsView gives much smoother zooming but a lower quality image, even when applying .setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) to the QGraphicsView.
My latest approach is to combine the two methods and use a QTransform on the QGraphicsView to have the smooth zooming but when the user releases the slider replace the image in the QGraphicsView with a scaled pixmap. This works great, but the position in the view is lost - the user zooms in to one area and because the scaled pixmap is larger the view jumps to another location when the slider is released and the higher quality scaled pixmap replaces the previous image.
I figured that as the width height ratio is the same in both images I could take the percentages of the scrollbars before the image swap and apply the same percentages after the swap and things should work out fine. This works well mostly, but there are still times when the view 'jumps' after swapping the image.
I'm pretty sure I'm doing something quite wrong here. Does anybody know of a better way to do this, or can anyone spot something in the code below that could cause this jumping?
This is the code to save/restore the scrollbar location. They are methods of a subclassed QGraphicsView:
def store_scrollbar_position(self):
x_max = self.horizontalScrollBar().maximum()
if x_max:
x = self.horizontalScrollBar().sliderPosition()
self.scroll_x_percentage = x * (100 / float(x_max))
y_max = self.verticalScrollBar().maximum()
if y_max:
y = self.verticalScrollBar().sliderPosition()
self.scroll_y_percentage = y * (100 / float(y_max))
def restore_scrollbar_position(self):
x_max = self.horizontalScrollBar().maximum()
if self.scroll_x_percentage and x_max:
x = x_max * (float(self.scroll_x_percentage) / 100)
self.horizontalScrollBar().setSliderPosition(x)
y_max = self.verticalScrollBar().maximum()
if self.scroll_y_percentage and y_max:
y = y_max * (float(self.scroll_y_percentage) / 100)
self.verticalScrollBar().setSliderPosition(y)
And here is how I'm doing the scaling. self.imageFile is a QPixmap and self.image is my QGraphicsPixmapItem. Again, part of a subclassed QGraphicsView. The method is attached to the slider movement with the highQuality parameter set to False. It is called again on slider release with highQuality as True to swap the image.
def setImageScale(self, scale=None, highQuality=True):
if self.imageFile.isNull():
return
if scale is None:
scale = self.scale
self.scale = scale
self.image.setPixmap(self.imageFile)
self.scene.setSceneRect(self.image.boundingRect())
self.image.setPos(0, 0)
if not highQuality:
self.setTransform(QTransform.fromScale(self.scaleFactor, self.scaleFactor))
self.store_scrollbar_position()
else:
self.image.setPixmap(self.imageFile.scaled(self.scaleFactor * self.imageFile.size(),
Qt.KeepAspectRatio, Qt.SmoothTransformation))
self.setTransform(self.transform)
self.scene.setSceneRect(self.image.boundingRect())
self.image.setPos(0, 0)
self.restore_scrollbar_position()
return
Any help would be appreciated. I'm starting to get quite frustrated with this now.
I found a solution that works better than the code I first posted. It's still not perfect, but is much improved. Just in case anyone else is trying to solve a similar problem...
When setting the low quality image I call this method added to my QGraphicsView:
def get_scroll_state(self):
"""
Returns a tuple of scene extents percentages.
"""
centerPoint = self.mapToScene(self.viewport().width()/2,
self.viewport().height()/2)
sceneRect = self.sceneRect()
centerWidth = centerPoint.x() - sceneRect.left()
centerHeight = centerPoint.y() - sceneRect.top()
sceneWidth = sceneRect.width()
sceneHeight = sceneRect.height()
sceneWidthPercent = centerWidth / sceneWidth if sceneWidth != 0 else 0
sceneHeightPercent = centerHeight / sceneHeight if sceneHeight != 0 else 0
return sceneWidthPercent, sceneHeightPercent
This gets stored in self.scroll_state. When setting the high quality image I call another function to restore the percentages used in the previous function.
def set_scroll_state(self, scroll_state):
sceneWidthPercent, sceneHeightPercent = scroll_state
x = (sceneWidthPercent * self.sceneRect().width() +
self.sceneRect().left())
y = (sceneHeightPercent * self.sceneRect().height() +
self.sceneRect().top())
self.centerOn(x, y)
This sets the center position to the same location (percentage-wise) as I was at before swapping the image.

Categories