Need advice on optimizing python 2d tileset rendering - python

I've been working on a 2D isometric tile-based MORPG for a few months now and realized that my game screen rendering is at a really low frame rate.
I've been researching and testing for a few weeks now and can only make marginal gains to my frame rate. I've used cProfile and tested my frame rate and I can achieve 100+ FPS on the program normally, but once my "render()" function is called it drops to 5 FPS.
Here is a (somewhat) condensed version of that function:
for y in range(0, 42):
for x in range(0, 42):
if (player.mapY + y - 21 > 0) and (player.mapY + y - 21 < 128) and (player.mapX + x - 21 > 0) and (
player.mapX + x - 21 < 128):
if (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) > -64 and (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX)+halfGraphicSizeX < 1024+32 and\
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY) > -32 and (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY)+halfGraphicSizeY < 600+32:
if self.getGroundAtYX(player.mapY + (y - 21), player.mapX + (x - 21)) is not 0:
canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY),
image=groundGraphics[
self.getGroundAtYX(player.mapY + (y - 21), player.mapX + (x - 21))],
anchor=NW)
if (self.getObjectAtYX(player.mapY + (y - 21), player.mapX + (x - 21)) is not 0):
canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
# -34 for img height diff between ground & objects
image=objectGraphics[
self.getObjectAtYX(player.mapY + (y - 21), player.mapX + (x - 21))],
anchor=NW)
ghostCopy = list(gameState.itemsOnGround)
for i in range(0, len(ghostCopy)):
if ghostCopy[i].idNum > 0:
if (player.mapX - 21 + x == ghostCopy[i].mapX and player.mapY - 21 + y ==
ghostCopy[i].mapY):
canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY),
image=itemGraphics[ghostCopy[i].idNum],
anchor=NW)
ghostCopy = ""
ghostCopy = list(gameState.monster)
for i in range(0, len(ghostCopy)):
if ghostCopy[i].active == True and ghostCopy[i].hp > 0:
if (player.mapX - 21 + x == ghostCopy[i].mapX and player.mapY - 21 + y ==
ghostCopy[i].mapY):
canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
# -34 for img height diff between ground & objects
image=monsterGraphics[ghostCopy[i].type],
anchor=NW)
canvas.create_rectangle(
(startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) + 15,
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 35),
(startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) + 16 + 33,
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 29), fill="black",
width=0)
canvas.create_rectangle(
(startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) + 16,
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 30),
(startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) + 16 + (
32 * (ghostCopy[i].hp / ghostCopy[i].maxHp)),
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34), fill="green",
width=0)
ghostCopy = ""
ghostCopy = list(gameState.sprite)
for i in range(0, len(ghostCopy)):
if ghostCopy[i].graphic[0:1] == "0":
if ghostCopy[i].active == True and ghostCopy[i].username != "ME":
if (player.mapX - 21 + x == ghostCopy[i].mapX and player.mapY - 21 + y ==
ghostCopy[i].mapY):
#"graphicToDraw" variable is derived from an animation state but has
#been removed from here to make it easier to read
canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
# -34 for img height diff between ground & objects
image=graphicToDraw,
anchor=NW)
if (y == 21):
if (x == 21):
#"graphicToDraw" variable is derived from an animation state but has
#been removed from here to make it easier to read
canvas.create_image(
(startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
# -34 for img height diff between ground & sprites
image=graphicToDraw,
anchor=NW)
ghostCopy = ""
ghostCopy = list(gameState.spells)
for i in range(0, len(ghostCopy)):
if ghostCopy[i].active:
if (player.mapX - 21 + x == ghostCopy[i].mapX and player.mapY - 21 + y ==
ghostCopy[i].mapY):
canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
(startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
image=spellGraphics[ghostCopy[i].id],
anchor=NW)
the render() function belongs to the map object (self refers to the map in this code segment). It effectively goes through the tiles -21 .. 21 on the x,y axis and if the tile is within the maps tile bounds (0 .. 128) and the tile is within the screen size (1024x600) it draws it to the screen.
The "ghostCopy" takes a snapshot of the current gamestate element (spells for example) so that it isn't updated by the thread receiving server data mid-iteration.
In some optimization testing I did I reduced the y, x range at the start to minimize the amount of total loop iterations.
I read that using a texture atlas/spritesheet could improve rendering speed but I couldn't make an improvement using one.
I tried just manually drawing the amount of images that would normally render in a general scene in a for loop and got around 30+ fps. So my render function is 25 fps slower than it could be.
I'm assuming that the constant checks every loop iteration to whether the tile is within the screen could be optimized, but i'm not really sure how to do that without using a loop like this.
If anyone has any recommendations I would greatly appreciate it.. i've been stuck on this problem for weeks and haven't made any real progress with my game at all :(
** [EDIT] **
Most recommendations seem to be towards limiting the amount of mathematical expressions. I haven't had a chance to test this, but is it likely just limiting the amount of math would optimize frame rate considerably?

You could pull all expressions involving constants and y to be computed just inside the outer (for y) loop; for example, player.mapY + y - 21, y * halfGraphicSizeX, y * halfGraphicSizeY, etc.: computer each just once, tuck in a variable, and use throughout the code. Similarly for x, but not quite as effective.

Here is an update of the first 19 lines of code that should improve the performance. In this example all I have done was reduce the total number of times you are preforming math operations.
for y in range(0, 42):
for x in range(0, 42):
player_y = player.mapY + y - 21
player_x = player.mapX + x -21
if player_y > 0 and player_y < 128 and player_x > 0 and player_x < 128:
start_drawing_x_half_graphic_size = startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX
start_drawing_y_half_graphic_size = startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY
if start_drawing_x_half_graphic_size > -64 and start_drawing_x_half_graphic_size + halfGraphicSizeX < 1024+32 and\
start_drawing_y_half_graphic_size > -32 and start_drawing_y_half_graphic_size + halfGraphicSizeY < 600+32:
if self.getGroundAtYX(player.mapY + (y - 21), player.mapX + (x - 21)) is not 0:
canvas.create_image(start_drawing_x_half_graphic_size,
start_drawing_y_half_graphic_size,
image=groundGraphics[
self.getGroundAtYX(player.mapY + (y - 21), player.mapX + (x - 21))],
anchor=NW)

Related

How to Create Piecewise Function in SymPy with Intervals

i need to create a piece-wise function inside an interval but sympy piecewise can't use and (&). I read that the function can't recieve Boolean values so I tried to add them together and it doesn't seem to be right. The code is as follows:
import numpy as np
import sympy as sp
import matplotlib as plt # This is all the library's i need
import mpmath
n = 10
x = sp.symbols('x', positive=True)
c = list(sp.symbols('c0:%d'%(n + 1)))
f = 1+(((sp.exp(x) * (1 - np.exp(-1))) + (sp.exp(-x)) * (np.exp(1) - 1)) / (np.exp(-1) - np.exp(1)))
xx = np.linspace(0, 1, n + 1)
i = 0
N = []
a = sp.Piecewise(
(0, x < float(xx[i])),
(((xx[i + 1] - x) / (xx[i + 1] - xx[i])), (x >= float((xx[i])))),
((xx[i + 1] - x) / (xx[i + 1] - xx[i]), x <= float((xx[i + 1]))),
(0, x > float(xx[i + 1])),
)
N.append(a)
for i in range(1, n):
a = sp.Piecewise(
(0, x < float(xx[i - 1])),
((xx[i - 1] - x) / xx[i - 1] - xx[i], x >= float((xx[i - 1]))),
((xx[i - 1] - x) / (xx[i - 1] - xx[i]), x <= float(xx[i])),
(0, x > float(xx[i])),
)
b = sp.Piecewise(
(0, x < float(xx[i])),
((xx[i + 1] - x) / (xx[i + 1] - xx[i]), x >= float(xx[i])),
((xx[i + 1] - x) / (xx[i + 1] - xx[i]), x <= float(xx[i + 1])),
(0, x > float(xx[i + 1])),
)
N.append(a + b)
i = i + 1
a = sp.Piecewise(
(0, x < float(xx[i - 1])),
((xx[i - 1] - x) / (xx[i - 1] - xx[i]), x >= float((xx[i - 1]))),
((xx[i - 1] - x) / (xx[i - 1] - xx[i]), x <= float(xx[i])),
(0, x > float(xx[i])),
)
N.append(a)
It would be helpful if you give an example of how Piecewise did not do what you wanted. Piecewise will work with And (and intervals expressed with the same):

check if point is inside a rotated rectangle

I am trying to check if a point is inside a rotated rectangle.
I found: https://gamedev.stackexchange.com/questions/128598/collision-detection-point-hitting-a-rotating-rectangle
but it doesnt work...
this is my python code:
def collides(point_x, point_y, x_rect, y_rect, width_rect, height_rect, center_rect_x, center_rect_y, angle_rect):
radians = angle_rect / 180.0 * math.pi
angle_sin = math.sin(radians)
angle_cos = math.cos(radians)
x = ((point_x - center_rect_x) * angle_cos - (point_y - center_rect_y) * angle_sin) + center _rect_x
y = ((point_x - center_rect_x) * angle_sin + (point_y - center_rect_y) * angle_cos) + center_rect_y
return x > x_rect and x < x_rect + width_rect and y > y_rect and y < y_rect + height_rect
how can I check if a point collides a rotated rectangle?

How can I make this PyTorch heatmap function faster and more efficient?

I have this function that creates a sort if heatmap for 2d tensors, but it's painfully slow when using larger tensor inputs. How can I speed it up and make it more efficient?
import torch
import numpy as np
import matplotlib.pyplot as plt
def heatmap(
tensor: torch.Tensor,
) -> torch.Tensor:
assert tensor.dim() == 2
def color_tensor(x: torch.Tensor) -> torch.Tensor:
if x < 0:
x = -x
if x < 0.5:
x = x * 2
return (1 - x) * torch.tensor(
[0.9686, 0.9686, 0.9686]
) + x * torch.tensor([0.5725, 0.7725, 0.8706])
else:
x = (x - 0.5) * 2
return (1 - x) * torch.tensor(
[0.5725, 0.7725, 0.8706]
) + x * torch.tensor([0.0196, 0.4431, 0.6902])
else:
if x < 0.5:
x = x * 2
return (1 - x) * torch.tensor(
[0.9686, 0.9686, 0.9686]
) + x * torch.tensor([0.9569, 0.6471, 0.5098])
else:
x = (x - 0.5) * 2
return (1 - x) * torch.tensor(
[0.9569, 0.6471, 0.5098]
) + x * torch.tensor([0.7922, 0.0000, 0.1255])
return torch.stack(
[torch.stack([color_tensor(x) for x in t]) for t in tensor]
).permute(2, 0, 1)
x = torch.randn(3,3)
x = x / x.max()
x_out = heatmap(x)
x_out = (x_out.permute(1, 2, 0) * 255).numpy()
plt.imshow(x_out.astype(np.uint8))
plt.axis("off")
plt.show()
An example of the output:
You need to get rid of ifs and the for loop and make a vectorized function. To do that, you can use masks and calculate all in one. Here it is:
def heatmap(tensor: torch.Tensor) -> torch.Tensor:
assert tensor.dim() == 2
# We're expanding to create one more dimension, for mult. to work.
xt = x.expand((3, x.shape[0], x.shape[1])).permute(1, 2, 0)
# this part is the mask: (xt >= 0) * (xt < 0.5) ...
# ... the rest is the original function translated
color_tensor = (
(xt >= 0) * (xt < 0.5) * ((1 - xt * 2) * torch.tensor([0.9686, 0.9686, 0.9686]) + xt * 2 * torch.tensor([0.9569, 0.6471, 0.5098]))
+
(xt >= 0) * (xt >= 0.5) * ((1 - (xt - 0.5) * 2) * torch.tensor([0.9569, 0.6471, 0.5098]) + (xt - 0.5) * 2 * torch.tensor([0.7922, 0.0000, 0.1255]))
+
(xt < 0) * (xt > -0.5) * ((1 - (-xt * 2)) * torch.tensor([0.9686, 0.9686, 0.9686]) + (-xt * 2) * torch.tensor([0.5725, 0.7725, 0.8706]))
+
(xt < 0) * (xt <= -0.5) * ((1 - (-xt - 0.5) * 2) * torch.tensor([0.5725, 0.7725, 0.8706]) + (-xt - 0.5) * 2 * torch.tensor([0.0196, 0.4431, 0.6902]))
).permute(2, 0, 1)
return color_tensor

Perlin noise looks streaky and not coherent

Now that my perlin generator is 'working' I created noise, to find that it is nothing like what I see on the internets...
My noise:
Notice the streaks:
What I am aiming to get (obviously with corresponding colour):
1:
Why does mine look so noisy and nasty?
Code (sorry for no stub, the Perlin noise makes up most of the program so it's important to include the full program):
from PIL import Image
from tkinter import filedialog
from random import randint, random
#Initialise width / height
width = 625
height = 625
#Import gradient picture - 200*1 image used to texture perlin noise
#R,G,B,Alpha
gradient = Image.open("image.png")
gradlist = list(gradient.getdata())
#Create new image
img = Image.new('RGBA', (width, height), color=(255, 255, 255, 255))
#Perlin noise modules --------------------------------------------------------------------------------------------------------
#Modules
from random import sample
from math import floor
p = sample([x for x in range(0, (width * height))], (width * height)) * 2
#Antialising
def fade(t):
retval = 6*(t**5) - 15*(t**4) + 10*(t**3)
return retval
#Linear interpolation
def lerp(t,a,b):
retval = a + (t * (b - a))
return retval
#Clever bitwise hash stuff - picks a unit vector from 12 possible - (1,1,0),(-1,1,0),(1,-1,0),(-1,-1,0),(1,0,1),(-1,0,1),(1,0,-1),(-1,0,-1),(0,1,1),(0,-1,1),(0,1,-1),(0,-1,-1)
def grad(hash, x, y, z):
h = hash % 15
if h < 8:
u = x
else:
u = y
if h < 4:
v = y
elif h in (12, 14):
v = x
else:
v = z
return (u if (h & 1) == 0 else -u) + (v if (h & 2) == 0 else -v)
#Perlin function
def perlin(x,y,z):
ix = int(floor(x)) & 255
iy = int(floor(y)) & 255
iz = int(floor(z)) & 255
x -= int(floor(x))
y -= int(floor(y))
z -= int(floor(z))
u = fade(x)
v = fade(y)
w = fade(z)
#Complicated hash stuff
A = p[ix] + iy
AA = p[A] + iz
AB = p[A + 1] + iz
B = p[ix + 1] + iy
BA = p[B] + iz
BB = p[B + 1] + iz
return -lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z),grad(p[BA], x - 1, y, z)),lerp(u, grad(p[AB], x, y - 1, z),grad(p[BB], x - 1, y - 1, z))),lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1),grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1),grad(p[BB + 1], x - 1, y - 1, z - 1))))
def octavePerlin(x,y,z,octaves,persistence):
total = 0
frequency = 1
amplitude = 1
maxValue = 0
for x in range(octaves):
total += perlin(x * frequency, y * frequency, z * frequency) * amplitude
maxValue += amplitude
amplitude *= persistence
frequency *= 2
return total / maxValue
z = random()
img.putdata([gradlist[int(octavePerlin((x + random() - 0.5) / 1000, (y + random() - 0.5) / 1000, z, 4, 2) * 100 + 100)] for x in range(width) for y in range(height)])
img.save(filedialog.asksaveasfilename() + ".png", "PNG")

How to rotate an ellipse pixel by pixel?

Right now, I am working on something (in Python) to create an ellipse and display it on the screen (in the console). I have the ellipse creation already, but rotating the ellipse gives me problems.
Ellipse Method:
def ellipse(yc, xc, b, a, rotation=0):
yc_min_b = yc - b
# divide b to account for spacing in console
b = int(round(b / 2 + 0.01)) - 1
yc = yc_min_b + b
points = []
a2 = a*a
b2 = b*b
fa2 = 4 * a2
fb2 = 4 * b2
x = 0
y = b
sigma = 2 * b2 + a2 * (1 - 2 * b)
while b2 * x <= a2 * y:
points.append((xc + x, yc + y))
points.append((xc - x, yc + y))
points.append((xc + x, yc - y))
points.append((xc - x, yc - y))
if sigma >= 0:
sigma += fa2 * (1 - y)
y -= 1
sigma += b2 * ((4 * x) + 6)
x += 1 # INCREMENT
x = a
y = 0
sigma = 2 * a2 + b2 * (1 - 2 * a)
while a2 * y <= b2 * x:
points.append((xc + x, yc + y))
points.append((xc - x, yc + y))
points.append((xc + x, yc - y))
points.append((xc - x, yc - y))
if sigma >= 0:
sigma += fb2 * (1 - x)
x -= 1
sigma += a2 * ((4 * y) + 6)
y += 1 # INCREMENT
# now rotate points
sin = math.sin(rotation)
cos = math.cos(rotation)
rotated = []
for point in points:
x = point[0]
y = point[1]
'''
px -= xc
py -= yc
xnew = px * c - py * s
ynew = px * s + py * c
px = xnew + xc
py = ynew + yc
'''
#XRot := Round(XCenter + (X - XCenter) * CAngle - (Y - YCenter) * SAngle);
#YRot := Round(YCenter + (X - XCenter) * SAngle + (Y - YCenter) * CAngle);
x = round(xc + (x + xc) * cos - (y - yc) * sin)
y = round(yc + (x - xc) * sin + (y - yc) * cos)
rotated.append((int(x), int(y)))
points = rotated
print points
ell_matr = []
# set up empty matrix
maxx = 0
maxy = 0
for point in points:
y = point[1]
x = point[0]
if y > maxy:
maxy = y
if x > maxx:
maxx = x
for i in range(maxy + 1):
ell_matr.append([])
for j in range(maxx + 1):
ell_matr[i].append(' ')
for point in points:
y = point[1]
x = point[0]
ell_matr[y][x] = fill
return ell_matr
I would ignore the matrix part, as it is translating the points into a matrix to display on screen.
Here is the output of an ellipse without rotation.
And when I add a 45 degree rotation (converted to radians)
Is there a better way to rotate the points?

Categories