Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tigercoding56/4391935c64fae79f182e303e98c4a23b to your computer and use it in GitHub Desktop.
Save tigercoding56/4391935c64fae79f182e303e98c4a23b to your computer and use it in GitHub Desktop.
lobster3d functions (credit to https://pypi.org/project/Lobster-3dEngine/#files i just added function for merging multiple objects)
from math import sin, cos, tan, sqrt, pi
class Object:
def __init__(self, position, vertices, faces, colors):
self.position = position
self.vertices = vertices
self.faces = faces
self.colors = colors
class Camera:
def __init__(self, position, rotation):
self.position = position
self.rotation = rotation
def get_rotation_matrix(self):
return rotation_matrix(*self.rotation)
class Vector:
def __init__(self, vector):
self.vector = vector
def __add__(self, other):
return Vector([a + b for a, b in zip(self.vector, other.vector)])
def __sub__(self, other):
return Vector([a - b for a, b in zip(self.vector, other.vector)])
def dot_product(self, other):
"""returns the dot product of two 3D vectors"""
return self.vector[0] * other.vector[0] + self.vector[1] * other.vector[1] + self.vector[2] * other.vector[2]
def cross_product(self, other):
"""returns the cross product of two 3D vectors"""
return (self.vector[1] * other.vector[2] - self.vector[2] * other.vector[1],
self.vector[2] * other.vector[0] - self.vector[0] * other.vector[2],
self.vector[0] * other.vector[1] - self.vector[1] * other.vector[0])
class Matrices:
def __init__(self, matrix):
self.matrix = matrix
self.rows = len(self.matrix)
self.columns = len(self.matrix[0])
def __mul__(self, other):
"""multiples 2 matrices together"""
assert self.columns == other.rows, "Multiplying matrix rules not met" # remove this line
new_matrix_dimensions = (self.rows, other.columns)
new_matrix = list()
for a in range(self.rows):
for b in range(other.columns):
value = 0
for c in range(other.rows):
value += self.matrix[a][c] * other.matrix[c][b]
new_matrix.append(round(value, 10))
new_matrix = [new_matrix[i:i + new_matrix_dimensions[1]]
for i in range(0, len(new_matrix), new_matrix_dimensions[1])]
return Matrices(new_matrix)
def init(screen_width, screen_height, field_of_view, z_near, z_far):
"""required to initiate this library"""
global _screen_height, _screen_width, _aspect_ratio, _field_of_view, _z_near, _z_far
_screen_width = screen_width
_screen_height = screen_height
_aspect_ratio = screen_height / screen_width
_field_of_view = field_of_view
_z_near = z_near
_z_far = z_far
def rotation_matrix(x, y, z) -> Matrices:
"""rotation matrix of x, y, z radians around x, y, z axes (respectively)"""
sx, cx = sin(x), cos(x)
sy, cy = sin(y), cos(y)
sz, cz = sin(z), cos(z)
return Matrices((
(cy * cz, -cy * sz, sy),
(cx * sz + sx * sy * cz, cx * cz - sz * sx * sy, -cy * sx),
(sz * sx - cx * sy * cz, cx * sz * sy + sx * cz, cx * cy)
))
def get_normal(vector0, vector1, vector2):
A = vector1 - vector0
B = vector2 - vector0
vector = A.cross_product(B)
magnitude = sqrt(vector[0] ** 2 + vector[1] ** 2 + vector[2] ** 2)
return vector[0] / magnitude, vector[1] / magnitude, vector[2] / magnitude
def projection(vertex):
if vertex[2] == 0:
vertex[2] += 0.001
field_of_view = 1 / tan(_field_of_view / 2)
q_val = _z_far / (_z_far - _z_near)
x = _aspect_ratio * field_of_view * vertex[0] / vertex[2]
y = field_of_view * vertex[1] / vertex[2]
z = vertex[2] * q_val - _z_near * q_val
return x, y, z
def display(obj: Object, cam: Camera):
points_list = []
camera_rotation = cam.get_rotation_matrix()
for faces, color in zip(obj.faces, obj.colors):
vertices = [projection((Vector((Matrices([obj.vertices[vertex]]) * camera_rotation).matrix[0]) +
Vector(obj.position) - Vector(cam.position)).vector) for vertex in faces]
normal_vector = Vector(get_normal(Vector(vertices[0][:3]), Vector(vertices[1][:3]), Vector(vertices[2][:3])))
if normal_vector.dot_product(Vector(vertices[0]) - Vector(cam.position)) < 0.2: # normal_vector.vector[2] < 0:
points = [(int((vertex[0] + 1) / 2 * _screen_width),
int(_screen_height - (vertex[1] + 1) / 2 * _screen_height)) for vertex in vertices]
points_list.append((points, vertices[0][2], color))
points_list = sorted(points_list, key=lambda z: z[1], reverse=True)
return points_list
def merge_objects(obj1, obj2):
merged_vertices = []
merged_faces = []
merged_colors = []
# Add the position of obj1 to its vertices
for vertex in obj1.vertices:
merged_vertices.append(tuple(v + p for v, p in zip(vertex, obj1.position)))
# Add the position of obj2 to its vertices
for vertex in obj2.vertices:
merged_vertices.append(tuple(v + p for v, p in zip(vertex, obj2.position)))
# Merge the faces and colors
merged_faces.extend(obj1.faces)
merged_faces.extend(obj2.faces)
merged_colors.extend(obj1.colors)
merged_colors.extend(obj2.colors)
merged_object = Object(vertices=merged_vertices,
position=[0, 0, 0],
faces=merged_faces,
colors=merged_colors)
return merged_object
import numpy as np
import cv2
def warp(surf, warp_pts,smooth=False):
"""Stretches a pygame surface to fill a quad using cv2's perspective warp.
Args:
surf: The surface to transform.
warp_pts: A list of four xy coordinates representing the polygon to fill.
Points should be specified in clockwise order starting from the top left.
smooth: Whether to use linear interpolation for the image transformation.
If false, nearest neighbor will be used.
out: An optional surface to use for the final output. If None or not
the correct size, a new surface will be made instead.
Returns:
[0]: A Surface containing the warped image.
[1]: A Rect describing where to blit the output surface to make its coordinates
match the input coordinates.
"""
if len(warp_pts) != 4:
raise ValueError("warp_pts must contain four points")
out = None
w, h = surf.get_size()
is_alpha = surf.get_flags() & pygame.SRCALPHA
# XXX throughout this method we need to swap x and y coordinates
# when we pass stuff between pygame and cv2. I'm not sure why .-.
src_corners = numpy.float32([(0, 0), (0, w), (h, w), (h, 0)])
quad = [tuple(reversed(p)) for p in warp_pts]
# find the bounding box of warp points
# (this gives the size and position of the final output surface).
min_x, max_x = float('inf'), -float('inf')
min_y, max_y = float('inf'), -float('inf')
for p in quad:
min_x, max_x = min(min_x, p[0]), max(max_x, p[0])
min_y, max_y = min(min_y, p[1]), max(max_y, p[1])
warp_bounding_box = pygame.Rect(int(min_x), int(min_y),
int(max_x - min_x),
int(max_y - min_y))
shifted_quad = [(p[0] - min_x, p[1] - min_y) for p in quad]
dst_corners = numpy.float32(shifted_quad)
mat = cv2.getPerspectiveTransform(src_corners, dst_corners)
orig_rgb = pygame.surfarray.pixels3d(surf)
flags = cv2.INTER_LINEAR if smooth else cv2.INTER_NEAREST
out_rgb = cv2.warpPerspective(orig_rgb, mat, warp_bounding_box.size, flags=flags)
if out is None or out.get_size() != out_rgb.shape[0:2]:
out = pygame.Surface(out_rgb.shape[0:2], pygame.SRCALPHA if is_alpha else 0)
pygame.surfarray.blit_array(out, out_rgb)
if is_alpha:
orig_alpha = pygame.surfarray.pixels_alpha(surf)
out_alpha = cv2.warpPerspective(orig_alpha, mat, warp_bounding_box.size, flags=flags)
alpha_px = pygame.surfarray.pixels_alpha(out)
alpha_px[:] = out_alpha
else:
out.set_colorkey(surf.get_colorkey())
# XXX swap x and y once again...
return out
def stretch_texture_to_polygon(screen, points, texture):
texture = warp(texture,points)
min_x = min(point[0] for point in points)
max_x = max(point[0] for point in points)
min_y = min(point[1] for point in points)
max_y = max(point[1] for point in points)
screen.blit(texture,(min_x, min_y))
#screen.blit(texture, (0, 0))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment