Camera_Projection_Type :: enum { ORTHOGRAPHIC; PERSPECTIVE; } Camera_Buffer_Data :: struct { projection_matrix: Matrix4; view_matrix: Matrix4; position: Vector4; } Camera :: struct { type : Camera_Projection_Type; position : Vector3; rotation : struct { yaw : float; pitch : float; roll : float; } aspect_ratio : float; fov : float; z_near : float; z_far : float; forward : Vector3; up : Vector3; right : Vector3; world_up : Vector3 = .{0,1,0}; projection_matrix : Matrix4; view_matrix : Matrix4; dirty : bool; } create_perspective_camera :: (position: Vector3 = .{}, fov: float, aspect: float, yaw: float = 0.0, pitch: float = 0.0, roll: float = 0.0, z_near: float = 0.1, z_far: float = 1000.0) -> Camera { camera : Camera; camera.type = .PERSPECTIVE; camera.world_up = .{0,1,0}; camera.position = position; camera.rotation.yaw = yaw; camera.rotation.pitch = pitch; camera.rotation.roll = roll; camera.aspect_ratio = aspect; camera.z_near = z_near; camera.z_far = z_far; camera.fov = fov; camera.projection_matrix = make_lh_projection_matrix(fov * (TAU / 360.0), aspect, z_near, z_far); update_view_matrix(*camera); return camera; } create_orthographic_camera :: (position: Vector3 = .{}, yaw: float = 0.0, pitch: float = 0.0, roll: float = 0.0, left: float, right: float, bottom: float, top: float, z_near: float = 0.1, z_far: float = 1000.0) -> Camera { camera : Camera; camera.type = .ORTHOGRAPHIC; camera.world_up = .{0,1,0}; camera.position = position; camera.rotation.yaw = yaw; camera.rotation.pitch = pitch; camera.rotation.roll = roll; camera.aspect_ratio = 0.1;//aspect; camera.z_near = z_near; camera.z_far = z_far; camera.fov = 0.0; camera.projection_matrix = orthographic_lh_projection_matrix(left, right, bottom, top, z_near, z_far); update_view_matrix(*camera); return camera; } orthographic_lh_projection_matrix :: (left: float, right: float, bottom: float, top: float, near: float, far: float) -> Matrix4 { m : Matrix4; width := right - left; height := top - bottom; m._11 = 2.0 / width; m._22 = 2.0 / height; m._33 = 1 / (far - near); m._34 = -near / (near-far); m._44 = 1.0; return m; } make_lh_projection_matrix :: (fov_vertical: float, aspect_ratio_horizontal_over_vertical: float, z_near: float, z_far: float, x_offset:=0.0, y_offset:=0.0, depth_range_01:=false) -> Matrix4 { result := Matrix4_Identity; tan_theta := tan(fov_vertical * 0.5); y_scale := 1 / tan_theta; x_scale := y_scale / aspect_ratio_horizontal_over_vertical; result._11 = x_scale; result._22 = y_scale; result._33 = z_far / (z_far - z_near); result._34 = -z_near * z_far / (z_far - z_near); result._43 = 1; result._44 = 0; result._13 = x_offset; // / w; result._23 = y_offset; // / h; if depth_range_01 { // To map -1,1 depth range to 0,1 we transform z as follows: z' = z * 0.5 + 0.5 result._33 = result._33 * 0.5 + result._43 * 0.5; result._34 = result._34 * 0.5 + result._44 * 0.5; } return result; } world_to_screen :: (camera: Camera, world_position: Vector3) -> Vector3 { pos : Vector4; pos.x = world_position.x; pos.y = world_position.y; pos.z = world_position.z; pos.w = 1.0; position := camera.projection_matrix * camera.view_matrix * pos; position.x /= position.w; position.y /= position.w; position.z /= position.w; screen_position : Vector3; screen_position.x = (position.x + 1.0) * 0.5 * cast(float)engine.renderer.render_target_width; screen_position.y = (position.y + 1.0) * 0.5 * cast(float)engine.renderer.render_target_height; screen_position.z = position.z; return screen_position; } screen_to_world :: (camera: Camera, screen_position: Vector2, distance: float) -> Vector3 { pos : Vector4; pos.x = (screen_position.x / cast(float)engine.renderer.render_target_width) * 2.0 - 1.0; pos.y = (screen_position.y / cast(float)engine.renderer.render_target_height) * 2.0 - 1.0; pos.z = 0.0; pos.w = 1.0; success, mat := inverse(camera.projection_matrix * camera.view_matrix); result := mat * pos; result.x /= result.w; result.y /= result.w; result.z /= result.w; // Calculate world direction by subtracting camera position and normalizing direction : Vector3; direction.x = result.x - camera.position.x; direction.y = result.y - camera.position.y; direction.z = result.z - camera.position.z; direction = normalize(direction); // Scale the direction to the specified distance world_position : Vector3; world_position.x = camera.position.x + direction.x * distance; world_position.y = camera.position.y + direction.y * distance; world_position.z = camera.position.z + direction.z * distance; return world_position; } normalized_screen_to_ray_v2 :: (camera: Camera, screen_position: Vector2) -> Ray { nds : Vector2; nds.x = (2.0 * screen_position.x) - 1.0; nds.y = (2.0 * screen_position.y) - 1.0; origin : Vector4; origin.x = nds.x; origin.y = nds.y; origin.z = 0.0; origin.w = 1.0; far : Vector4; far.x = nds.x; far.y = nds.y; far.z = 1.0; far.w = 1.0; success :, inverse_view_proj := inverse(camera.projection_matrix * camera.view_matrix); ray_origin := inverse_view_proj * origin; ray_end := inverse_view_proj * far; // The hero we didn't know we needed ray_origin /= ray_origin.w; ray_end /= ray_end.w; ray : Ray; ray.origin = to_v3(ray_origin); ray.direction = normalize(to_v3(ray_end) - ray.origin); return ray; } screen_to_ray_v2 :: (camera: Camera, screen_position: Vector2, screen_size: Vector2) -> Ray { nds : Vector2; nds.x = (2.0 * screen_position.x) / screen_size.x - 1.0; nds.y = (2.0 * screen_position.y) / screen_size.y - 1.0; origin : Vector4; origin.x = nds.x; origin.y = nds.y; origin.z = 0.0; origin.w = 1.0; far : Vector4; far.x = nds.x; far.y = nds.y; far.z = 1.0; far.w = 1.0; success :, inverse_view_proj := inverse(camera.projection_matrix * camera.view_matrix); ray_origin := inverse_view_proj * origin; ray_end := inverse_view_proj * far; // The hero we didn't know we needed ray_origin /= ray_origin.w; ray_end /= ray_end.w; ray : Ray; ray.origin = to_v3(ray_origin); ray.direction = normalize(to_v3(ray_end) - ray.origin); return ray; } screen_to_ray :: (camera: *Camera, screen_position: Vector2, screen_size: Vector2) -> Ray { ray : Ray; ray.origin = camera.position; ray_nds : Vector3; ray_nds.x = (2.0 * screen_position.x) / screen_size.x - 1.0; ray_nds.y = (2.0 * screen_position.y) / screen_size.y - 1.0; ray_nds.z = 0.0; ray_clip : Vector4; ray_clip.x = ray_nds.x; ray_clip.y = ray_nds.y; ray_clip.z = -1.0; ray_clip.w = 1.0; success :, ray_eye := inverse(camera.projection_matrix) * ray_clip; ray_eye.z = 1.0; ray_eye.w = 0.0; success2 :, inv_cam_matrix := inverse(camera.view_matrix); ray_world := to_v3(inv_cam_matrix * ray_eye); ray.direction = normalize(ray_world); return ray; } set_fov :: (camera: *Camera, fov: float) { camera.fov = fov; camera.projection_matrix = make_lh_projection_matrix(fov * (TAU / 360.0), camera.aspect_ratio, camera.z_near, camera.z_far); camera.dirty = true; } set_position :: (camera: *Camera, position: Vector3) { camera.position = position; update_view_matrix(camera); } set_yaw :: (camera: *Camera, yaw: float) { camera.rotation.yaw = yaw; update_view_matrix(camera); } set_pitch :: (camera: *Camera, pitch: float) { camera.rotation.pitch = pitch; update_view_matrix(camera); } set_roll :: (camera: *Camera, roll: float) { camera.rotation.roll = roll; update_view_matrix(camera); } set_pitch_yaw_roll :: (camera: *Camera, pitch: float, yaw: float, roll: float) { camera.rotation.pitch = pitch; camera.rotation.yaw = yaw; camera.rotation.roll = roll; update_view_matrix(camera); } update_view_matrix :: (using camera: *Camera) { camera.rotation.pitch = clamp(camera.rotation.pitch, -89.0, 89.0); pitch := rotation.pitch * DEGREES_TO_RADIANS; yaw := rotation.yaw * DEGREES_TO_RADIANS; roll := rotation.roll * DEGREES_TO_RADIANS; pitch_mat := Matrix4.{ 1, 0, 0, 0, 0, cos(pitch), sin(pitch), 0, 0, -sin(pitch), cos(pitch), 0, 0, 0, 0, 1}; yaw_mat := Matrix4.{ cos(yaw), 0, -sin(yaw), 0, 0, 1, 0, 0, sin(yaw), 0, cos(yaw), 0, 0, 0, 0, 1}; roll_mat := Matrix4.{ cos(roll), sin(roll), 0, 0, -sin(roll), cos(roll), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; matrix := yaw_mat * pitch_mat * roll_mat; camera.forward = .{0,0,1}; camera.right = .{1,0,0}; camera.up = .{0,1,0}; camera.forward = normalize(to_v3(matrix * Vector4.{camera.forward.x, camera.forward.y, camera.forward.z, 0.0})); camera.right = normalize(to_v3(matrix * Vector4.{camera.right.x, camera.right.y, camera.right.z, 0.0})); camera.up = normalize(to_v3(matrix * Vector4.{camera.up.x, camera.up.y, camera.up.z, 0.0})); eye := camera.position + camera.forward; //camera.forward = normalize(direction); //camera.right = normalize(cross_product(Vector3.{0,1,0}, camera.forward)); //camera.up = normalize(cross_product(camera.forward, camera.right)); m := Matrix4_Identity; m._11 = camera.right.x; m._12 = camera.right.y; m._13 = camera.right.z; m._14 = -dot(camera.right, eye); m._21 = camera.up.x; m._22 = camera.up.y; m._23 = camera.up.z; m._24 = -dot(camera.up, eye); m._31 = camera.forward.x; m._32 = camera.forward.y; m._33 = camera.forward.z; m._34 = -dot(camera.forward, eye); m._41 = 0.0; m._42 = 0.0; m._43 = 0.0; m._44 = 1.0; camera.view_matrix = m; camera.dirty = true; }