I have been trying to make a 3d engine with python for some time, and I have gotten pretty far, however I have found a problem in when I try and sort items in a list, the sorting flips when you are close enough to the cube.
main.py:
import os
os.environ["SDL_VIDEO_CENTERED"] = '1'
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = '1'
import pygame
from pygame import gfxdraw
import math
from matrix import matrix_multiplication
import mesh
from random import randint as random
import time
startTime = time.time()
black, white, blue = (20, 20, 20), (230, 230, 230), (0, 154, 255)
width, height = 700, 700
pygame.init()
pygame.display.set_caption("3D Engine")
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
frames = 60
outline = False
rs, gs, bs = [random(0, 255) for i in range(len(mesh.faces))], [random(0, 255) for i in
range(len(mesh.faces))], [random(0, 255) for i in range(len(mesh.faces))]
angle_x = 0
angle_y = 0
angle_z = 0
pos_x = 0
pos_y = 0
pos_z = 0
cube_position = [width//2, height//2]
scale = 600
speed = 0.001
points = [[[i] for i in j] for j in mesh.verts]
movSpeed = 0.001
font = pygame.font.SysFont("Corbel", 23)
def avarageX(i):
return (new_points[mesh.faces[i][0]][0][0] + new_points[mesh.faces[i][1]][0][0] + new_points[mesh.faces[i][2]][0][0] + new_points[mesh.faces[i][3]][0][0]) / 4
def avarageY(i):
return (new_points[mesh.faces[i][0]][1][0] + new_points[mesh.faces[i][1]][1][0] + new_points[mesh.faces[i][2]][1][0] + new_points[mesh.faces[i][3]][1][0]) / 4
def avarageZ(i):
return (new_points[mesh.faces[i][0]][2][0] + new_points[mesh.faces[i][1]][2][0] + new_points[mesh.faces[i][2]][2][0] + new_points[mesh.faces[i][3]][2][0]) / 4
def distToCam(i):
a = [0, 0, 0]
b = [avarageX(i), avarageY(i), avarageZ(i)]
return math.dist(a, b)
print("It took: {} seconds".format(time.time() - startTime))
run = True
while run:
dt = clock.tick(frames)
fps = clock.get_fps()
screen.fill(white)
keys = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
index = 0
projected_points = [j for j in range(len(points))]
rotation_x = [[1, 0, 0],
[0, math.cos(angle_x), -math.sin(angle_x)],
[0, math.sin(angle_x), math.cos(angle_x)]]
rotation_y = [[math.cos(angle_y), 0, -math.sin(angle_y)],
[0, 1, 0],
[math.sin(angle_y), 0, math.cos(angle_y)]]
rotation_z = [[math.cos(angle_z), -math.sin(angle_z), 0],
[math.sin(angle_z), math.cos(angle_z), 0],
[0, 0, 1]]
new_points = []
for point in points:
rotated_2d = matrix_multiplication(rotation_y, point)
rotated_2d = matrix_multiplication(rotation_x, rotated_2d)
rotated_2d = matrix_multiplication(rotation_z, rotated_2d)
new_point = [[rotated_2d[0][0] + pos_x], [rotated_2d[1][0] + pos_y], [rotated_2d[2][0] - pos_z]]
new_points.append(new_point)
distance = 5
z = 1 / (distance - new_point[2][0])
projection_matrix = [[z, 0, 0],
[0, z, 0]]
projected_2d = matrix_multiplication(projection_matrix, new_point)
x = int(projected_2d[0][0] * scale) + cube_position[0]
y = int(projected_2d[1][0] * scale) + cube_position[1]
projected_points[index] = [x, y]
index += 1
zs = [[distToCam(i), i] for i in range(len(mesh.faces))]
zs.sort(reverse=True)
faces = [[mesh.faces[zs[i][1]], zs[i][1]] for i in range(len(mesh.faces))]
fi = 0
for f in faces:
gfxdraw.filled_polygon(screen, [projected_points[f[0][0]], projected_points[f[0][1]], projected_points[f[0][2]], projected_points[f[0][3]]], (rs[zs[fi][1]], gs[zs[fi][1]], bs[zs[fi][1]]))
gfxdraw.aapolygon(screen, [projected_points[f[0][0]], projected_points[f[0][1]], projected_points[f[0][2]], projected_points[f[0][3]]], (rs[zs[fi][1]], gs[zs[fi][1]], bs[zs[fi][1]]))
fi += 1
angle_x += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * speed * dt
angle_y += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed * dt
pos_x += (keys[pygame.K_d] - keys[pygame.K_a]) * movSpeed * dt
pos_z += (keys[pygame.K_w] - keys[pygame.K_s]) * movSpeed * dt
text = font.render(str(round(fps, 1)), False, black)
screen.blit(text, (0, 0))
pygame.display.update()
pygame.quit()
The matrix multiplication
matrix.py:
def matrix_multiplication(a, b):
columns_a = len(a[0])
rows_a = len(a)
columns_b = len(b[0])
rows_b = len(b)
result_matrix = [[j for j in range(columns_b)] for i in range(rows_a)]
if columns_a == rows_b:
for x in range(rows_a):
for y in range(columns_b):
sum = 0
for k in range(columns_a):
sum += a[x][k] * b[k][y]
result_matrix[x][y] = sum
return result_matrix
else:
print("columns of the first matrix must be equal to the rows of the second matrix")
return None
The mesh data.
mesh.py:
verts = [
[1, 1, 1],
[1, 1, -1],
[1, -1, 1],
[1, -1, -1],
[-1, 1, 1],
[-1, 1, -1],
[-1, -1, 1],
[-1, -1, -1]
]
faces = [
[0, 4, 6, 2],
[3, 2, 6, 7],
[7, 6, 4, 5],
[5, 1, 3, 7],
[1, 0, 2, 3],
[5, 4, 0, 1]
]
WARNING: There might be flashing lights on startup
You have to compute the distance of the camera position ([0, 0, distance]) to the points in world space (new_points), instead of the points in model space (points):
def distToCam(i):
a = [0, 0, distance]
b = [sum(new_points[mesh.faces[i][pi]][j][0] for pi in range(4)) / 4 for j in range(3)]
return math.dist(a, b)
Related
I want to add some code that will allow me to output a mp4. I've tried adding; import moviepy.editor as moviepy, import os with no avail. I'm super new and i'm not sure on the right question to ask. Do I have to use import cv2, or what i think is; pygame.Surface.save() function and save the frames then convernt them in ffmpeg? I want to generate a 30 second to 1 min clip directly converted to a mp4.
import pygame
import math
import os
from matrix import matrix_multiplication
from bresenham import bresenham
from numpy import interp
os.environ['SDL_VIDEO_CENTERED'] = '1'
background, bright = (13,13,13), (60,180,120)
width, height = 800, 800
pygame.init()
pygame.display.set_caption('ASCII TESSERACT')
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
fps = 60
#ASCII FONTS
lineFont = pygame.font.SysFont('scheherazade', 20, bold=False)
cornerFont = pygame.font.SysFont('nachlieliclm', 24, bold=True)
#Tesseract information
angle = 0
cube_position = [width//2, height//2]
scale = 2800
speed = 0.005
points = [n for n in range(16)]
points[0] = [[-1], [-1], [1], [1]]
points[1] = [[1], [-1], [1], [1]]
points\[2\] = \[\[1\], \[1\], \[1\], \[1\]\]
points\[3\] = \[\[-1\], \[1\], \[1\], \[1\]\]
points\[4\] = \[\[-1\], \[-1\], \[-1\], \[1\]\]
points\[5\] = \[\[1\], \[-1\], \[-1\], \[1\]\]
points\[6\] = \[\[1\], \[1\], \[-1\], \[1\]\]
points\[7\] = \[\[-1\], \[1\], \[-1\], \[1\]\]
points\[8\] = \[\[-1\], \[-1\], \[1\], \[-1\]\]
points\[9\] = \[\[1\], \[-1\], \[1\], \[-1\]\]
points\[10\] = \[\[1\], \[1\], \[1\], \[-1\]\]
points\[11\] = \[\[-1\], \[1\], \[1\], \[-1\]\]
points\[12\] = \[\[-1\], \[-1\], \[-1\], \[-1\]\]
points\[13\] = \[\[1\], \[-1\], \[-1\], \[-1\]\]
points\[14\] = \[\[1\], \[1\], \[-1\], \[-1\]\]
points\[15\] = \[\[-1\], \[1\], \[-1\], \[-1\]\]
def connect_point(i, j, k, offset, lineChar = ':', skip = 7):
a = k\[i + offset\]
b = k\[j + offset\]
line = bresenham(a\[0\], a\[1\], b\[0\], b\[1\])
s = skip
for point in line:
s -= 1
if s == 0:
# display ASCII character
text_display(lineChar, point[0], point[1])
if s < 0:
s = skip
def text_display(letter, x_pos, y_pos):
text = lineFont.render(str(letter), True, bright)
screen.blit(text, (x_pos, y_pos))
def corner_display(x, y, z, w, interpolateColor = True, fontSizeInterpolate = True):
\# Interpolate z and brightness
if interpolateColor:
interpolatedColor = (interp(z, \[0.1, 0.27\], \[background\[0\], bright\[0\]\]), interp(z, \[0.1, 0.27\], \[background\[1\], bright\[1\]\]), interp(z, \[0.1, 0.27\], \[background\[2\], bright\[2\]\]))
else:
interpolatedColor = bright
# Interpolate w and font size
if fontSizeInterpolate:
fontSize = round(int(interp(w, [0.1, 0.27], [50, 76])))
cornerFont = pygame.font.SysFont('nachlieliclm', fontSize, bold=True)
text = cornerFont.render('.', True, interpolatedColor)
screen.blit(text, (x, y-fontSize / 2))
# Pygame loop
run = True
while run:
clock.tick(fps)
screen.fill(background)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYUP:
if event.key == pygame.K_ESCAPE:
run = False
index = 0
projected_points = [j for j in range(len(points))]
# 3D matrix rotations
rotation_x = [[1, 0, 0],
[0, math.cos(angle), -math.sin(angle)],
[0, math.sin(angle), math.cos(angle)]]
rotation_y = [[math.cos(angle), 0, -math.sin(angle)],
[0, 1, 0],
[math.sin(angle), 0, math.cos(angle)]]
rotation_z = [[math.cos(angle), -math.sin(angle), 0],
[math.sin(angle), math.cos(angle), 0],
[0, 0 ,1]]
tesseract_rotation = [[1, 0, 0],
[0, math.cos(-math.pi/2), -math.sin(-math.pi/2)],
[0, math.sin(-math.pi/2), math.cos(-math.pi/2)]]
# 4D matrix rotations
rotation4d_xy= [[math.cos(angle), -math.sin(angle), 0, 0],
[math.sin(angle), math.cos(angle), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]]
rotation4d_xz = [[math.cos(angle), 0, -math.sin(angle), 0],
[0, 1, 0, 0],
[math.sin(angle), 0, math.cos(angle), 0],
[0, 0, 0, 1]]
rotation4d_xw = [[math.cos(angle), 0, 0, -math.sin(angle)],
[0, 1, 0, 0],
[0, 0, 1, 0],
[math.sin(angle), 0, 0, math.cos(angle)]]
rotation4d_yz = [[1, 0, 0, 0],
[0, math.cos(angle), -math.sin(angle), 0],
[0, math.sin(angle), math.cos(angle), 0],
[0, 0, 0, 1]]
rotation4d_yw = [[1, 0, 0, 0],
[0, math.cos(angle), 0, -math.sin(angle)],
[0, 0, 1, 0],
[0, math.sin(angle), 0, math.cos(angle)]]
rotation4d_zw = [[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, math.cos(angle), -math.sin(angle)],
[0, 0, math.sin(angle), math.cos(angle)]]
for point in points:
rotated_3d = matrix_multiplication(rotation4d_xy, point)
rotated_3d = matrix_multiplication(rotation4d_zw, rotated_3d)
distance = 5
w = 1/(distance - rotated_3d[3][0])
projection_matrix4 = [
[w, 0, 0, 0],
[0, w, 0, 0],
[0, 0, w, 0],]
projected_3d = matrix_multiplication(projection_matrix4, rotated_3d)
rotated_2d = matrix_multiplication(tesseract_rotation, projected_3d)
z = 1/(distance - (rotated_2d[2][0] + rotated_3d[3][0]))
projection_matrix = [[z, 0, 0],
[0, z, 0]]
rotated_2d = matrix_multiplication(rotation_x, projected_3d)
projected_2d = matrix_multiplication(projection_matrix, rotated_2d)
x = int(projected_2d[0][0] * scale) + cube_position[0]
y = int(projected_2d[1][0] * scale) + cube_position[1]
projected_points[index] = [x, y, z, w]
corner_display(x, y, z, w)
index += 1
#draw edges
for m in range(4):
connect_point(m, (m+1)%4, projected_points, 8)
connect_point(m+4, (m+1)%4 + 4, projected_points, 8)
connect_point(m, m+4, projected_points, 8)
for m in range(4):
connect_point(m, (m+1)%4, projected_points, 0)
connect_point(m+4, (m+1)%4 + 4, projected_points, 0)
connect_point(m, m+4, projected_points, 0)
for m in range(8):
connect_point(m, m+8, projected_points, 0)
angle += speed
pygame.display.update()
pygame.quit()
You are correct, you cannot save pygame directly into mp4 and you must use pygame.Surface.save() to save each frame into a .jpg, make it into an array, then make it into an mp4. You can change how often it saves the frame by updating the framecount.
import moviepy.editor import *
import moviepy.editor as mp
import os
import glob
import shutil
#Put all your pygame code in here
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
while not exiting:
# handle events
# paint the screen
# save the frame
frame_count += 1
filename = "screen_%04d.png" % (frame_count)
pygame.image.save(window, filename) #this saves it in the file
clock.tick( WINDOW_FPS )
src_dir = "your/current/dir" #This is where pygame saves the images
image_dir = "your/destination/dir" #This is where you want the dir to be
for jpgfile in glob.iglob(os.path.join(src_dir, "*.jpg")):
shutil.copy(jpgfile, dst_dir)
#Makes the array of images into imgarr
for filename in os.listdir(image_dir):
if filename.endswith(".jpg") or filename.endswith(".png"):
imgarr.append(os.path.join(image_dir, filename))
slides = []
for n, url in enumerate(imgarr): #All Images in imagedir
slides.append(mp.ImageClip(url).set_fps(1).set_duration(1))
video.mp.concatenate_videoclips(slides)
videoclip.write_videofile("video_1.mp4")
Im working on a ray-caster as a project in pygame and i've stumbled upon an issue where the ray casts in the opposite direction from angles 90 to 270. Ive tried making the DDA algorithm subtract the x and y values instead of add them but it does nothing. The ray-cast code starts at line 73, here is the code:
import pygame
import sys
import math
# initialise pygame, set display and clock
WIDTH = 600
HEIGHT = 600
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
#map
world = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
TILE = WIDTH/len(world)
tile_list = []
for row_num, row in enumerate(world):
for col_num, col in enumerate(row):
if col == 1:
x, y = col_num * TILE, row_num * TILE
rect = pygame.Rect(x, y, TILE, TILE)
tile_list.append(rect)
planeX = 0
planeY = 0.66
dirX = -1
dirY = 0
player_rect = pygame.Rect(60, 60, 30, 30)
vel = 0.5
fps = 60
#sets the origin for all vectors
startpoint = pygame.math.Vector2(player_rect[0] + player_rect[2]/2, player_rect[1] + player_rect[3]/2)
#sets the differnt endpoints for all vectors
endpoint = pygame.math.Vector2(15,0)
fov = 60
angle = 0
angle_rest = angle - fov//2
radian = 0.01745329
lineStartPoint = startpoint
lineEndPoint = endpoint
turnRate = 5
#DDA algorithm/raycasting
def castRay(rayAngle):
slope = math.tan(radian * rayAngle)
COLLIDE = False
x = startpoint[0]
y = startpoint[1]
#loop that adds 1 to x and the slope to y until it collides with a wall
while not COLLIDE and x < WIDTH and x > 0:
x += 1
y += slope
point = pygame.Rect(x,y, 1,1)
for tile in tile_list:
rect = pygame.Rect(tile)
if(point.colliderect(rect)):
COLLIDE = True
#draws line for this loop
pygame.draw.line(screen, (255,0,0), startpoint, (x, y))
x = startpoint[0]
y = startpoint[1]
#loop that adds 1/slope to x and 1 to y until it collides with a wall
while not COLLIDE and y < HEIGHT and y > 0:
x += 1/slope
y += 1
point = pygame.Rect(x,y, 1,1)
for tile in tile_list:
rect = pygame.Rect(tile)
if(point.colliderect(rect)):
COLLIDE = True
pygame.draw.line(screen, (0,0,255), startpoint, (x, y))
while True:
clock.tick(fps)
screen.fill((135, 53, 53))
# get pressed keys
keys = pygame.key.get_pressed()
# delta x and delta y values
dx, dy = 0, 0
startpoint = pygame.math.Vector2(player_rect[0] + player_rect[2]/2, player_rect[1] + player_rect[3]/2)
current_endpoint = startpoint + endpoint.rotate(angle)
lineStartPoint = current_endpoint + endpoint.rotate((angle+90) % 360)
lineEndPoint = current_endpoint - endpoint.rotate((angle+90) % 360)
angle_rest = (angle - fov//2) % 360
#movement
if keys[pygame.K_UP]:
dx -= (startpoint[0] - current_endpoint[0])*vel
dy -= (startpoint[1] - current_endpoint[1])*vel
if keys[pygame.K_DOWN]:
dx += (startpoint[0] - current_endpoint[0])*vel
dy += (startpoint[1] - current_endpoint[1])*vel
if keys[pygame.K_RIGHT]:
angle = (angle+turnRate) % 360
if keys[pygame.K_LEFT]:
angle = (angle-turnRate) % 360
# this is where collision detection starts
x_projection = player_rect.move(dx, 0)
y_projection = player_rect.move(0, dy)
for tile in tile_list:
pygame.draw.rect(screen, (99, 99, 99), tile)
if x_projection.colliderect(tile):
if dx > 0:
dx = tile.left - player_rect.right
elif dx < 0:
dx = tile.right - player_rect.left
elif y_projection.colliderect(tile):
if dy > 0:
dy = tile.top - player_rect.bottom
elif dy < 0:
dy = tile.bottom - player_rect.top
player_rect.move_ip(dx, dy)
pygame.draw.rect(screen, (38, 136, 126), player_rect)
pygame.draw.line(screen , (255,0,0), startpoint, current_endpoint)
pygame.draw.line(screen, 255, (lineStartPoint), (lineEndPoint))
castRay(angle)
print(angle)
# remember to update the display
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
If the angle is between 90° and 270°, you must reverse the direction:
def castRay(rayAngle):
slope = math.tan(math.radians(rayAngle))
dx = 1
if rayAngle > 90 and rayAngle < 270:
slope *= -1
dx *= -1
COLLIDE = False
x = startpoint[0]
y = startpoint[1]
#loop that adds 1 to x and the slope to y until it collides with a wall
while not COLLIDE and x < WIDTH and x > 0:
x += dx
y += slope
point = pygame.Rect(x,y, 1,1)
for tile in tile_list:
rect = pygame.Rect(tile)
if(point.colliderect(rect)):
COLLIDE = True
However, I suggest to simplify your code. Compute the direction vector:
dx, dy = math.cos(angleRad), math.sin(angleRad)
The components of the direction vector by the reciprocal maximum component so that the larger component becomes 1:
s = max(abs(dx), abs(dy))
dx /= s
dy /= s
function castRay:
def castRay(rayAngle):
angleRad = math.radians(rayAngle)
dx, dy = math.cos(angleRad), math.sin(angleRad)
s = max(abs(dx), abs(dy))
dx /= s
dy /= s
COLLIDE = False
x, y = startpoint
while not COLLIDE and 0 <= y < WIDTH and 0 < y < HEIGHT:
x += dx
y += dy
point = pygame.Rect(x,y, 1,1)
for tile in tile_list:
rect = pygame.Rect(tile)
if(point.colliderect(rect)):
COLLIDE = True
pygame.draw.line(screen, (255,0,0), startpoint, (x, y))
I am trying to warp an image based of the orientation of the camera relative to an aruco marker in the middle of the image. I have managed to get the translation part working but the rotation element is not working. It seems like the image isn't rotating about the centre of the aruco axis. The reference image was taken straight on and the warped image is overlayed.
# Find centre of the marker
top_left_x = (corners[0][0][0, 0])
top_left_y = (corners[0][0][0, 1])
top_right_x = (corners[0][0][1, 0])
top_right_y = (corners[0][0][1, 1])
bottom_right_x = (corners[0][0][2, 0])
bottom_right_y = (corners[0][0][2, 1])
bottom_left_x = (corners[0][0][3, 0])
bottom_left_y = (corners[0][0][3, 1])
# Compare this to the centre of the image to calculate the offset
mid_x = top_right_x - (top_right_x - top_left_x) / 2
mid_y = bottom_left_y - (bottom_left_y - top_left_y) / 2
x_centre = 960
y_centre = 540
x_offset = x_centre - mid_x
y_offset = y_centre - mid_y
if x_centre > mid_x: # gone right
x_offset = 1 * (x_centre - mid_x) # correction to the left
if x_centre < mid_x: # gone left
x_offset = -1 * (mid_x - x_centre) # correction to the right
if y_centre > mid_y: # gone down
y_offset = 1 * (y_centre - mid_y) # correction to the left
if y_centre < mid_y: # gone left
y_offset = -1 * (mid_y - y_centre) # correction to the right
current_z_distance = (math.sqrt((pos_camera[0]**2) + (pos_camera[1]**2) +
(pos_camera[2]**2))) * 15.4
img = cv2.imread('Corrected.png')
corrected_z = 31 # Distance when image was taken
initial_z_distance = corrected_z * 15.4 # Pixels
delta_z = (initial_z_distance - current_z_distance)
scale_factor = current_z_distance / initial_z_distance # how much larger the image
now is. Used for scaling
z_translation = delta_z * 1.54 # how much the image has moved. negative for going
backwards
z_translation = 0
z_axis = 960 / scale_factor
proj2dto3d = np.array([[1, 0, -mid_x],
[0, 1, -mid_y],
[0, 0, 0],
[0, 0, 1]], np.float32)
proj3dto2d = np.array([[z_axis, 0, mid_x, 0],
[0, z_axis, mid_y, 0], # defines to centre of rotation
[0, 0, 1, 0]], np.float32)
trans = np.array([[1, 0, 0, x_offset * -1], # Working
[0, 1, 0, y_offset * -1],
[0, 0, 1, 960], # keep as 960
[0, 0, 0, 1]], np.float32)
x = math.degrees(roll_marker) * -1 # forwards and backwards
y = math.degrees(pitch_marker) * -1 # Left and right
z = 0
rx = np.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]], np.float32) #
ry = np.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]], np.float32)
rz = np.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]], np.float32)
ax = float(x * (math.pi / 180.0)) # 0
ay = float(y * (math.pi / 180.0))
az = float(z * (math.pi / 180.0)) # 0
rx[1, 1] = math.cos(ax) # 0
rx[1, 2] = -math.sin(ax) # 0
rx[2, 1] = math.sin(ax) # 0
rx[2, 2] = math.cos(ax) # 0
ry[0, 0] = math.cos(ay)
ry[0, 2] = -math.sin(ay)
ry[2, 0] = math.sin(ay)
ry[2, 2] = math.cos(ay)
rz[0, 0] = math.cos(az) # 0
rz[0, 1] = -math.sin(az) # 0
rz[1, 0] = math.sin(az) # 0
rz[1, 1] = math.cos(az) # 0
# Translation matrix
# r = rx.dot(ry) # if we remove the lines we put r=ry
r = rx.dot(ry) # order may need to be changed
final = proj3dto2d.dot(trans.dot(r.dot(proj2dto3d))) # just rotation
dst = cv2.warpPerspective(img, final, (img.shape[1], img.shape[0]), None, cv2.INTER_LINEAR, cv2.BORDER_CONSTANT, (255, 255, 255))
This question already has answers here:
Does Python make a copy of objects on assignment?
(5 answers)
Closed 2 years ago.
Here's the full code. I've left out few unnecessary things
import random
import pygame
FPS = 1
WIDTH, HEIGHT = 400, 400
RESOLUTION = 40
GRAY = (200, 200, 200)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game of Life")
def draw_grid(win, cols, rows):
for i in range(cols):
for j in range(rows):
x = i * RESOLUTION
y = j * RESOLUTION
pygame.draw.rect(win, GRAY, (x, y, RESOLUTION, RESOLUTION), 1)
def make_2d_array(cols, rows):
arr = []
for i in range(cols):
arr.append([])
for j in range(rows):
arr[i].append(0)
return arr
def count_neighbours(grid, x, y):
neighbourCount = 0
for i in range(-1, 2):
for j in range(-1, 2):
neighbourCount += grid[x + i][y + j]
return neighbourCount
def draw_squares(win, grid, cols, rows):
#nextA = make_2d_array(cols, rows)
nextA = grid
for i in range(len(grid)):
for j in range(len(grid[i])):
x = i * RESOLUTION
y = j * RESOLUTION
if grid[i][j] == 1:
pygame.draw.rect(win, WHITE, (x, y, RESOLUTION, RESOLUTION))
elif grid[i][j] == 0:
pygame.draw.rect(win, BLACK, (x, y, RESOLUTION, RESOLUTION))
for i in range(cols):
for j in range(rows):
if i == 0 or i == cols-1 or j == 0 or j == rows-1:
nextA[i][j] = grid[i][j]
else:
state = grid[i][j]
neighbours = count_neighbours(grid, i, j)
if state == 0 and neighbours == 3:
nextA[i][j] = 1
elif state == 1 and (neighbours < 2 or neighbours > 3):
nextA[i][j] = 0
else:
nextA[i][j] = state
grid = nextA
def main():
run = True
clock = pygame.time.Clock()
cols = int(WIDTH / RESOLUTION)
rows = int(HEIGHT / RESOLUTION)
grid = make_2d_array(cols, rows)
"""for i in range(cols):
for j in range(rows):
grid[i][j] = random.randint(0, 1)"""
#glider test
grid = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
draw_squares(WIN, grid, cols, rows)
draw_grid(WIN, cols, rows)
pygame.display.update()
pygame.quit()
main()
I was following along with a tutorial that was written in Java or JavaScript but the rules are the same and should work but they don't.
The rules simplified should be this:
if state == 0 and neighbours == 3:
nextA[i][j] = 1
elif state == 1 and (neighbours < 2 or neighbours > 3):
nextA[i][j] = 0
else:
nextA[i][j] = state
but when I run the code the first if-statement works I'm pretty sure (it's kinda hard to understand the program behaves weirdly).
The implementation of the rules is correct, but in Conway's Game of Life you have to create a new and empty grid every turn. The fields in the new grid must be determined depending on the fields in the current grid and the evolution rules.
Create a new and empty grid in draw_squares, but return the new grid form the function:
def draw_squares(win, grid, cols, rows):
# nextA = grid # <--- DELETE
nextA = make_2d_array(cols, rows) # <--- ADD
# [...]
return nextA # <-- return the new grid
Make the new grid the current grid by assigning the new grid returned by draw_squares to grid:
grid = draw_squares(WIN, grid, cols, rows)
Additionally the computation of the neighbors is wrong. A field is not a neighbor of itself:
def count_neighbours(grid, x, y):
neighbourCount = 0
for i in range(-1, 2):
for j in range(-1, 2):
if i != 0 or j != 0:
neighbourCount += grid[x + i][y + j]
return neighbourCount
I have a vector y that either increments or decrements by 1. What I want is to create a another vector y2 that shows when y1 changed direction.
I can't figure out how to do this in numpy.
EDIT updated plot, due to missing comma in y
import matplotlib.pyplot as plt
import numpy as np
EDIT: Fixed missing comma in y
import matplotlib.pyplot as plt
import numpy as np
y = np.array([0,0,1,1,2,2,1,0,-1,-1,0,0,1])
x = np.arange(len(y))
y2 = np.array([0,0,1,0,0,0,-1,0,0,0,1,0,0])
plt.plot(x, y, label='y - Actual')
plt.plot(x, y2, label='y2 - Desired')
plt.legend()
plt.show()
Edit: Now also handles out of spec inputs (increments outside -1, 0, 1).
Here's a slightly faster (for the time being) way (pp is me, D is #Divakar):
# n = 10
# pp 0.02363790 ms
# D 0.03705720 ms
# n = 1000
# pp 0.03609150 ms
# D 0.05877410 ms
# n = 1000000
# pp 22.63471480 ms
# D 36.92147740 ms
Code including benchmarking:
import numpy as np
import types
from timeit import timeit
def setup_data(n, allow_out_of_spec=True):
if allow_out_of_spec:
data = {'y': np.cumsum(np.random.randint(0, 10, (n,))
* np.random.randint(-1, 2, (n,)))}
else:
data = {'y': np.cumsum(np.random.randint(-1, 2, (n,)))}
return data
# mine
def f_pp(y, allow_out_of_spec=True):
if allow_out_of_spec:
d = np.sign(np.diff(y))
else:
d = np.diff(y)
ud = np.flatnonzero(d)
uds = d[ud]
chng = ud[np.r_[True, uds[1:] != uds[:-1]]]
out = np.zeros(len(y), dtype=int)
out[1:][chng] = d[chng]
return out
# #Divakar's
def f_D(y):
s0 = np.flatnonzero(y[1:] > y[:-1])+1
s1 = np.flatnonzero(y[1:] < y[:-1])+1
idx0 = np.searchsorted(s1,s0,'right')
s0c = s0[np.r_[True,idx0[1:] > idx0[:-1]]]
idx1 = np.searchsorted(s0c,s1,'right')
s1c = s1[np.r_[True,idx1[1:] > idx1[:-1]]]
out = np.zeros(len(y),dtype=int)
out[s0c] = 1
out[s1c] = -1
return out
for n in (10, 1000, 1000000):
data = setup_data(n)
ref = np.array(f_pp(**data))
print(f'n = {n}')
for name, func in list(globals().items()):
if not name.startswith('f_') or not isinstance(func, types.FunctionType):
continue
try:
assert np.allclose(ref, func(**data))
print("{:16s}{:16.8f} ms".format(name[2:], timeit(
'f(**data)', globals={'f':func, 'data':data}, number=10)*100))
except:
print("{:16s} apparently failed".format(name[2:]))
Here's one way -
def detect_ups_downs(y):
s0 = np.flatnonzero(y[1:] > y[:-1])+1
s1 = np.flatnonzero(y[1:] < y[:-1])+1
idx0 = np.searchsorted(s1,s0,'right')
s0c = s0[np.r_[True,idx0[1:] > idx0[:-1]]]
idx1 = np.searchsorted(s0c,s1,'right')
s1c = s1[np.r_[True,idx1[1:] > idx1[:-1]]]
out = np.zeros(len(y),dtype=int)
out[s0c] = 1
out[s1c] = -1
return out
Sample run -
In [92]: y = np.array([0,0,1,2,3,4,1,0,-1-1,0,0,1,0,8,8,9,-4,-6,4,-2,2])
In [93]: np.c_[y, detect_ups_downs(y)]
Out[93]:
array([[ 0, 0],
[ 0, 0],
[ 1, 1],
[ 2, 0],
[ 3, 0],
[ 4, 0],
[ 1, -1],
[ 0, 0],
[-2, 0],
[ 0, 1],
[ 0, 0],
[ 1, 0],
[ 0, -1],
[ 8, 1],
[ 8, 0],
[ 9, 0],
[-4, -1],
[-6, 0],
[ 4, 1],
[-2, -1],
[ 2, 1]])
Try taking a derivative:
dy = y[1:] - y[:-1]
Then check if adjacent points have switched derivative signs:
increasing = dy > 0
decreasing = dy < 0
saddle = dy == 0
change_increasing = increasing[1:] and decreasing[:-1]
change_decreasing = decreasing[1:] and increasing[:-1]
Bring all that information together:
changes = np.zeros_like(dy)
changes[0] = (1 * increasing[0]) + (-1 * decreasing[0])
changes[1:][change_increasing] = 1
changes[1:][change_decreasing] = -1
changes[saddle] = 0
print(changes)