|
|
"""
|
|
|
3D transformation module for GPU-accelerated geometry processing
|
|
|
"""
|
|
|
from typing import Tuple, Optional, Union
|
|
|
import numpy as np
|
|
|
|
|
|
class Transform3D:
|
|
|
"""GPU-accelerated 3D transformation operations"""
|
|
|
|
|
|
def __init__(self, driver):
|
|
|
self.driver = driver
|
|
|
self.prefix = "transform3d"
|
|
|
|
|
|
def projection_matrix(
|
|
|
self,
|
|
|
fov_y: float = 60.0,
|
|
|
aspect: float = 1.0,
|
|
|
near: float = 0.1,
|
|
|
far: float = 1000.0,
|
|
|
perspective: bool = True
|
|
|
) -> str:
|
|
|
"""Create projection matrix in driver memory"""
|
|
|
if perspective:
|
|
|
|
|
|
f = 1.0 / np.tan(np.radians(fov_y) / 2.0)
|
|
|
mat = np.zeros((4, 4), dtype=np.float32)
|
|
|
mat[0, 0] = f / aspect
|
|
|
mat[1, 1] = f
|
|
|
mat[2, 2] = (far + near) / (near - far)
|
|
|
mat[2, 3] = 2.0 * far * near / (near - far)
|
|
|
mat[3, 2] = -1.0
|
|
|
else:
|
|
|
|
|
|
mat = np.zeros((4, 4), dtype=np.float32)
|
|
|
mat[0, 0] = 2.0 / aspect
|
|
|
mat[1, 1] = 2.0
|
|
|
mat[2, 2] = -2.0 / (far - near)
|
|
|
mat[2, 3] = -(far + near) / (far - near)
|
|
|
mat[3, 3] = 1.0
|
|
|
|
|
|
mat_name = f"{self.prefix}_proj_matrix"
|
|
|
self.driver.create_tensor(mat_name, mat)
|
|
|
return mat_name
|
|
|
|
|
|
def view_matrix(
|
|
|
self,
|
|
|
eye: Tuple[float, float, float],
|
|
|
target: Tuple[float, float, float],
|
|
|
up: Tuple[float, float, float] = (0, 1, 0)
|
|
|
) -> str:
|
|
|
"""Create view matrix in driver memory"""
|
|
|
eye = np.array(eye, dtype=np.float32)
|
|
|
target = np.array(target, dtype=np.float32)
|
|
|
up = np.array(up, dtype=np.float32)
|
|
|
|
|
|
|
|
|
forward = target - eye
|
|
|
forward = forward / np.linalg.norm(forward)
|
|
|
|
|
|
right = np.cross(forward, up)
|
|
|
right = right / np.linalg.norm(right)
|
|
|
|
|
|
up = np.cross(right, forward)
|
|
|
up = up / np.linalg.norm(up)
|
|
|
|
|
|
|
|
|
mat = np.zeros((4, 4), dtype=np.float32)
|
|
|
mat[0, :3] = right
|
|
|
mat[1, :3] = up
|
|
|
mat[2, :3] = -forward
|
|
|
mat[0, 3] = -np.dot(right, eye)
|
|
|
mat[1, 3] = -np.dot(up, eye)
|
|
|
mat[2, 3] = np.dot(forward, eye)
|
|
|
mat[3, 3] = 1.0
|
|
|
|
|
|
mat_name = f"{self.prefix}_view_matrix"
|
|
|
self.driver.create_tensor(mat_name, mat)
|
|
|
return mat_name
|
|
|
|
|
|
def model_matrix(
|
|
|
self,
|
|
|
translation: Tuple[float, float, float] = (0, 0, 0),
|
|
|
rotation: Tuple[float, float, float] = (0, 0, 0),
|
|
|
scale: Union[float, Tuple[float, float, float]] = 1.0
|
|
|
) -> str:
|
|
|
"""Create model matrix in driver memory"""
|
|
|
|
|
|
if isinstance(scale, (int, float)):
|
|
|
scale = (scale, scale, scale)
|
|
|
sx, sy, sz = scale
|
|
|
|
|
|
|
|
|
rx, ry, rz = map(np.radians, rotation)
|
|
|
|
|
|
rot_x = np.array([
|
|
|
[1, 0, 0, 0],
|
|
|
[0, np.cos(rx), -np.sin(rx), 0],
|
|
|
[0, np.sin(rx), np.cos(rx), 0],
|
|
|
[0, 0, 0, 1]
|
|
|
], dtype=np.float32)
|
|
|
|
|
|
rot_y = np.array([
|
|
|
[np.cos(ry), 0, np.sin(ry), 0],
|
|
|
[0, 1, 0, 0],
|
|
|
[-np.sin(ry), 0, np.cos(ry), 0],
|
|
|
[0, 0, 0, 1]
|
|
|
], dtype=np.float32)
|
|
|
|
|
|
rot_z = np.array([
|
|
|
[np.cos(rz), -np.sin(rz), 0, 0],
|
|
|
[np.sin(rz), np.cos(rz), 0, 0],
|
|
|
[0, 0, 1, 0],
|
|
|
[0, 0, 0, 1]
|
|
|
], dtype=np.float32)
|
|
|
|
|
|
|
|
|
tx, ty, tz = translation
|
|
|
|
|
|
|
|
|
mat = np.eye(4, dtype=np.float32)
|
|
|
mat = mat @ rot_z @ rot_y @ rot_x
|
|
|
mat[0, 0] *= sx; mat[1, 1] *= sy; mat[2, 2] *= sz
|
|
|
mat[0:3, 3] = [tx, ty, tz]
|
|
|
|
|
|
mat_name = f"{self.prefix}_model_matrix"
|
|
|
self.driver.create_tensor(mat_name, mat)
|
|
|
return mat_name
|
|
|
|
|
|
def transform_vertices(
|
|
|
self,
|
|
|
vertices_name: str,
|
|
|
model_matrix_name: Optional[str] = None,
|
|
|
view_matrix_name: Optional[str] = None,
|
|
|
proj_matrix_name: Optional[str] = None
|
|
|
) -> str:
|
|
|
"""Transform vertices through model-view-projection pipeline"""
|
|
|
vertices = self.driver.get_tensor(vertices_name)
|
|
|
|
|
|
|
|
|
if vertices.shape[-1] == 3:
|
|
|
ones = np.ones((*vertices.shape[:-1], 1), dtype=vertices.dtype)
|
|
|
vertices = np.concatenate([vertices, ones], axis=-1)
|
|
|
homogeneous_name = f"{self.prefix}_homogeneous"
|
|
|
self.driver.create_tensor(homogeneous_name, vertices)
|
|
|
vertices_name = homogeneous_name
|
|
|
|
|
|
result_name = vertices_name
|
|
|
|
|
|
|
|
|
if model_matrix_name is not None:
|
|
|
result_name = f"{self.prefix}_model_result"
|
|
|
self.driver.matmul(vertices_name, model_matrix_name, out=result_name)
|
|
|
|
|
|
|
|
|
if view_matrix_name is not None:
|
|
|
view_result = f"{self.prefix}_view_result"
|
|
|
self.driver.matmul(result_name, view_matrix_name, out=view_result)
|
|
|
result_name = view_result
|
|
|
|
|
|
|
|
|
if proj_matrix_name is not None:
|
|
|
proj_result = f"{self.prefix}_proj_result"
|
|
|
self.driver.matmul(result_name, proj_matrix_name, out=proj_result)
|
|
|
result_name = proj_result
|
|
|
|
|
|
|
|
|
if self.driver.get_tensor(proj_matrix_name)[3, 2] != 0:
|
|
|
verts = self.driver.get_tensor(result_name)
|
|
|
w = verts[..., 3:]
|
|
|
verts = verts / w
|
|
|
self.driver.create_tensor(result_name, verts)
|
|
|
|
|
|
return result_name
|
|
|
|
|
|
def normal_matrix(self, model_matrix_name: str) -> str:
|
|
|
"""Create normal matrix (inverse transpose of 3x3 model matrix)"""
|
|
|
model_mat = self.driver.get_tensor(model_matrix_name)
|
|
|
normal_mat = np.linalg.inv(model_mat[:3, :3]).T
|
|
|
|
|
|
mat_name = f"{self.prefix}_normal_matrix"
|
|
|
self.driver.create_tensor(mat_name, normal_mat)
|
|
|
return mat_name
|
|
|
|
|
|
def transform_normals(
|
|
|
self,
|
|
|
normals_name: str,
|
|
|
normal_matrix_name: str
|
|
|
) -> str:
|
|
|
"""Transform normal vectors using normal matrix"""
|
|
|
result_name = f"{self.prefix}_transformed_normals"
|
|
|
self.driver.matmul(normals_name, normal_matrix_name, out=result_name)
|
|
|
|
|
|
|
|
|
normals = self.driver.get_tensor(result_name)
|
|
|
normals = normals / np.linalg.norm(normals, axis=-1, keepdims=True)
|
|
|
self.driver.create_tensor(result_name, normals)
|
|
|
|
|
|
return result_name
|
|
|
|