You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
74 lines
2.7 KiB
74 lines
2.7 KiB
import math |
|
|
|
def project_3d_to_2d(observer, look_at, fov_h, fov_v, screen_width, screen_height, point_3d): |
|
""" |
|
Project a 3D point onto a 2D screen. |
|
|
|
Args: |
|
observer: The observer's position in 3D space (x, y, z). |
|
look_at: The point the observer is looking at in 3D space (x, y, z). |
|
fov_h: Horizontal field of view in radians. |
|
fov_v: Vertical field of view in radians. |
|
screen_width: Width of the 2D screen. |
|
screen_height: Height of the 2D screen. |
|
point_3d: The 3D point to project (x, y, z). |
|
|
|
Returns: |
|
The 2D coordinates of the projected point (x, y) on the screen. |
|
""" |
|
# Step 1: Calculate the forward, right, and up vectors |
|
def normalize(v): |
|
length = math.sqrt(sum(coord ** 2 for coord in v)) |
|
return tuple(coord / length for coord in v) |
|
|
|
forward = normalize((look_at[0] - observer[0], look_at[1] - observer[1], look_at[2] - observer[2])) |
|
right = normalize(( |
|
forward[1] * 0 - forward[2] * 1, |
|
forward[2] * 0 - forward[0] * 0, |
|
forward[0] * 1 - forward[1] * 0 |
|
)) |
|
up = ( |
|
right[1] * forward[2] - right[2] * forward[1], |
|
right[2] * forward[0] - right[0] * forward[2], |
|
right[0] * forward[1] - right[1] * forward[0] |
|
) |
|
|
|
# Step 2: Transform the 3D point into the observer's coordinate system |
|
relative_point = ( |
|
point_3d[0] - observer[0], |
|
point_3d[1] - observer[1], |
|
point_3d[2] - observer[2] |
|
) |
|
x_in_view = sum(relative_point[i] * right[i] for i in range(3)) |
|
y_in_view = sum(relative_point[i] * up[i] for i in range(3)) |
|
z_in_view = sum(relative_point[i] * forward[i] for i in range(3)) |
|
|
|
# Step 3: Perform perspective projection |
|
if z_in_view <= 0: |
|
raise ValueError("The point is behind the observer and cannot be projected.") |
|
|
|
aspect_ratio = screen_width / screen_height |
|
tan_fov_h = math.tan(fov_h / 2) |
|
tan_fov_v = math.tan(fov_v / 2) |
|
|
|
ndc_x = x_in_view / (z_in_view * tan_fov_h * aspect_ratio) |
|
ndc_y = y_in_view / (z_in_view * tan_fov_v) |
|
|
|
# Step 4: Map normalized device coordinates (NDC) to screen coordinates |
|
screen_x = (ndc_x + 1) / 2 * screen_width |
|
screen_y = (1 - ndc_y) / 2 * screen_height |
|
|
|
return screen_x, screen_y |
|
|
|
|
|
# Example usage |
|
observer = (0, 0, 0) # Observer's position |
|
look_at = (0, 0, 1) # Observer is looking along the positive Z-axis |
|
fov_h = math.radians(90) # Horizontal FOV in radians |
|
fov_v = math.radians(60) # Vertical FOV in radians |
|
screen_width = 800 # Screen width |
|
screen_height = 600 # Screen height |
|
point_3d = (1, 1, 5) # The 3D point to project |
|
|
|
projected_2d_point = project_3d_to_2d(observer, look_at, fov_h, fov_v, screen_width, screen_height, point_3d) |
|
print("Projected 2D Point:", projected_2d_point) |