Hunyuan3D-2.2-mrp-MLX/hy3dpaint/DifferentiableRenderer/camera_utils_mlx.py
modelbeast e4cfa9d1e9 Clean MLX build for MODELBEAST (inference-only)
Fork of dgrauet/Hunyuan3D-2.1-mlx + our generate_e2e.py CLI, env-tunable
remesh (HY3D_REMESH_FACES), and HARDWARE.md. Upstream training data
(mini_trainset) and demo images stripped — inference needs none of it.
Full upstream history: github.com/dgrauet/Hunyuan3D-2.1-mlx
2026-07-16 14:38:14 +10:00

120 lines
3.1 KiB
Python

"""Camera utilities for MLX mesh rendering.
Direct port of camera_utils.py from PyTorch to MLX.
Projection matrices stay as numpy (computed once, small).
transform_pos operates on MLX arrays.
"""
import math
import mlx.core as mx
import numpy as np
def transform_pos(mtx, pos, keepdim=False):
"""Transform positions by a 4x4 matrix.
Args:
mtx: (4, 4) numpy or mx.array transformation matrix.
pos: (N, 3) or (N, 4) mx.array positions.
keepdim: if False, prepend batch dim -> (1, N, 4).
Returns:
(1, N, 4) or (N, 4) mx.array of transformed positions.
"""
if isinstance(mtx, np.ndarray):
t_mtx = mx.array(mtx)
else:
t_mtx = mtx
if pos.shape[-1] == 3:
ones = mx.ones((pos.shape[0], 1), dtype=pos.dtype)
posw = mx.concatenate([pos, ones], axis=1)
else:
posw = pos
result = posw @ t_mtx.T
if keepdim:
return result
return mx.expand_dims(result, axis=0)
def get_mv_matrix(elev, azim, camera_distance, center=None):
"""Build world-to-camera matrix from spherical coordinates.
Returns:
(4, 4) numpy float32 array.
"""
elev = -elev
azim += 90
elev_rad = math.radians(elev)
azim_rad = math.radians(azim)
camera_position = np.array([
camera_distance * math.cos(elev_rad) * math.cos(azim_rad),
camera_distance * math.cos(elev_rad) * math.sin(azim_rad),
camera_distance * math.sin(elev_rad),
])
if center is None:
center = np.array([0, 0, 0])
else:
center = np.array(center)
lookat = center - camera_position
lookat = lookat / np.linalg.norm(lookat)
up = np.array([0, 0, 1.0])
right = np.cross(lookat, up)
right = right / np.linalg.norm(right)
up = np.cross(right, lookat)
up = up / np.linalg.norm(up)
c2w = np.concatenate(
[np.stack([right, up, -lookat], axis=-1), camera_position[:, None]],
axis=-1,
)
w2c = np.zeros((4, 4), dtype=np.float32)
w2c[:3, :3] = c2w[:3, :3].T
w2c[:3, 3:] = -c2w[:3, :3].T @ c2w[:3, 3:]
w2c[3, 3] = 1.0
return w2c
def get_orthographic_projection_matrix(
left=-1, right=1, bottom=-1, top=1, near=0, far=2
):
"""Orthographic projection matrix.
Returns:
(4, 4) numpy float32 array.
"""
m = np.eye(4, dtype=np.float32)
m[0, 0] = 2 / (right - left)
m[1, 1] = 2 / (top - bottom)
m[2, 2] = -2 / (far - near)
m[0, 3] = -(right + left) / (right - left)
m[1, 3] = -(top + bottom) / (top - bottom)
m[2, 3] = -(far + near) / (far - near)
return m
def get_perspective_projection_matrix(fovy, aspect_wh, near, far):
"""Perspective projection matrix.
Returns:
(4, 4) numpy float32 array.
"""
fovy_rad = math.radians(fovy)
return np.array(
[
[1.0 / (math.tan(fovy_rad / 2.0) * aspect_wh), 0, 0, 0],
[0, 1.0 / math.tan(fovy_rad / 2.0), 0, 0],
[0, 0, -(far + near) / (far - near), -2.0 * far * near / (far - near)],
[0, 0, -1, 0],
],
dtype=np.float32,
)