Files
coven/core/camera.jai
2025-06-30 23:34:34 +02:00

355 lines
10 KiB
Plaintext

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;
}