|
import numpy as np |
|
from scipy.spatial.transform import Rotation as R |
|
|
|
import torch |
|
|
|
def dot(x, y): |
|
if isinstance(x, np.ndarray): |
|
return np.sum(x * y, -1, keepdims=True) |
|
else: |
|
return torch.sum(x * y, -1, keepdim=True) |
|
|
|
|
|
def length(x, eps=1e-20): |
|
if isinstance(x, np.ndarray): |
|
return np.sqrt(np.maximum(np.sum(x * x, axis=-1, keepdims=True), eps)) |
|
else: |
|
return torch.sqrt(torch.clamp(dot(x, x), min=eps)) |
|
|
|
|
|
def safe_normalize(x, eps=1e-20): |
|
return x / length(x, eps) |
|
|
|
|
|
def look_at(campos, target, opengl=True): |
|
|
|
|
|
|
|
if not opengl: |
|
|
|
forward_vector = safe_normalize(target - campos) |
|
up_vector = np.array([0, 1, 0], dtype=np.float32) |
|
right_vector = safe_normalize(np.cross(forward_vector, up_vector)) |
|
up_vector = safe_normalize(np.cross(right_vector, forward_vector)) |
|
else: |
|
|
|
forward_vector = safe_normalize(campos - target) |
|
up_vector = np.array([0, 1, 0], dtype=np.float32) |
|
right_vector = safe_normalize(np.cross(up_vector, forward_vector)) |
|
up_vector = safe_normalize(np.cross(forward_vector, right_vector)) |
|
R = np.stack([right_vector, up_vector, forward_vector], axis=1) |
|
return R |
|
|
|
|
|
|
|
def orbit_camera(elevation, azimuth, radius=1, is_degree=True, target=None, opengl=True): |
|
|
|
|
|
|
|
|
|
if is_degree: |
|
elevation = np.deg2rad(elevation) |
|
azimuth = np.deg2rad(azimuth) |
|
x = radius * np.cos(elevation) * np.sin(azimuth) |
|
y = - radius * np.sin(elevation) |
|
z = radius * np.cos(elevation) * np.cos(azimuth) |
|
if target is None: |
|
target = np.zeros([3], dtype=np.float32) |
|
campos = np.array([x, y, z]) + target |
|
T = np.eye(4, dtype=np.float32) |
|
T[:3, :3] = look_at(campos, target, opengl) |
|
T[:3, 3] = campos |
|
return T |
|
|
|
|
|
class OrbitCamera: |
|
def __init__(self, W, H, r=2, fovy=60, near=0.01, far=100): |
|
self.W = W |
|
self.H = H |
|
self.radius = r |
|
self.fovy = np.deg2rad(fovy) |
|
self.near = near |
|
self.far = far |
|
self.center = np.array([0, 0, 0], dtype=np.float32) |
|
self.rot = R.from_matrix(np.eye(3)) |
|
self.up = np.array([0, 1, 0], dtype=np.float32) |
|
|
|
@property |
|
def fovx(self): |
|
return 2 * np.arctan(np.tan(self.fovy / 2) * self.W / self.H) |
|
|
|
@property |
|
def campos(self): |
|
return self.pose[:3, 3] |
|
|
|
|
|
@property |
|
def pose(self): |
|
|
|
res = np.eye(4, dtype=np.float32) |
|
res[2, 3] = self.radius |
|
|
|
rot = np.eye(4, dtype=np.float32) |
|
rot[:3, :3] = self.rot.as_matrix() |
|
res = rot @ res |
|
|
|
res[:3, 3] -= self.center |
|
return res |
|
|
|
|
|
@property |
|
def view(self): |
|
return np.linalg.inv(self.pose) |
|
|
|
|
|
@property |
|
def perspective(self): |
|
y = np.tan(self.fovy / 2) |
|
aspect = self.W / self.H |
|
return np.array( |
|
[ |
|
[1 / (y * aspect), 0, 0, 0], |
|
[0, -1 / y, 0, 0], |
|
[ |
|
0, |
|
0, |
|
-(self.far + self.near) / (self.far - self.near), |
|
-(2 * self.far * self.near) / (self.far - self.near), |
|
], |
|
[0, 0, -1, 0], |
|
], |
|
dtype=np.float32, |
|
) |
|
|
|
|
|
@property |
|
def intrinsics(self): |
|
focal = self.H / (2 * np.tan(self.fovy / 2)) |
|
return np.array([focal, focal, self.W // 2, self.H // 2], dtype=np.float32) |
|
|
|
@property |
|
def mvp(self): |
|
return self.perspective @ np.linalg.inv(self.pose) |
|
|
|
def orbit(self, dx, dy): |
|
|
|
side = self.rot.as_matrix()[:3, 0] |
|
rotvec_x = self.up * np.radians(-0.05 * dx) |
|
rotvec_y = side * np.radians(-0.05 * dy) |
|
self.rot = R.from_rotvec(rotvec_x) * R.from_rotvec(rotvec_y) * self.rot |
|
|
|
def scale(self, delta): |
|
self.radius *= 1.1 ** (-delta) |
|
|
|
def pan(self, dx, dy, dz=0): |
|
|
|
self.center += 0.0005 * self.rot.as_matrix()[:3, :3] @ np.array([-dx, -dy, dz]) |