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
|
10 months ago
|
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)
|