Created
March 24, 2021 05:09
-
-
Save PranavSK/59fe9ec5f6f6a137313c8a5dd0df7260 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| extends Node | |
| export (float, 0.0, 10.0) var margin_offset: = 5.0 | |
| export (float, 4.0, 100.0) var boom_length: = 16 | |
| export (float) var move_weight: = 5.0 | |
| var _follow_target_bounds: = AABB() | |
| var _velocity: = Vector3.ZERO | |
| var _tan_vertical_fov: = 0.0 | |
| var _tan_horizontal_fov: = 0.0 | |
| onready var _camera: = $Camera | |
| func _ready(): | |
| _precalculate_fov() | |
| # warning-ignore:return_value_discarded | |
| get_viewport().connect("size_changed", self, "_precalculate_fov") | |
| # NOTE: Precalulate on camera fov change as well? | |
| func _process(delta): | |
| _update_follow_target_bounds() | |
| var camera_pos = _calculate_camera_position() | |
| _update_movement(camera_pos, delta) | |
| # NOTE: Can we also do camera orbit? | |
| func _update_follow_target_bounds(): | |
| var targets = get_tree().get_nodes_in_group("camera_follow_targets") | |
| _follow_target_bounds = targets[0].get_view_aabb() | |
| for target in targets: | |
| _follow_target_bounds = _follow_target_bounds.merge( | |
| target.get_view_aabb() | |
| ) | |
| func _calculate_camera_position(): | |
| # The projection plane is taken as center of the follow_bounds. | |
| # The idea is that if the objects are behind the projection plane, we find | |
| # the minimal rect of their 'shadows'. This would mean to zoom into the | |
| # projection plane. | |
| # Else, we find the maximal rect of their 'shadows' and thus zoom out from | |
| # the projection plane. | |
| # Below this is implicitly handled by the sign of `distance_to_proj_plane`. | |
| # Take the -ve since camera faces -ve z. | |
| var projection_plane_depth: float = -_camera.to_local( | |
| MathUtils.get_aabb_center(_follow_target_bounds) | |
| ).z | |
| # Next to calculate the rect of the target bounds on the projection plane. | |
| # We want max values of rect.x_max and rect.y_max | |
| # Set the initial values to -inf. | |
| var proj_rect_x_max = -INF | |
| var proj_rect_y_max = -INF | |
| # We want min values of rect.x_min and rect.y_min | |
| # Set the initial values to +inf. | |
| var proj_rect_x_min = INF | |
| var proj_rect_y_min = INF | |
| for idx in 8: | |
| # In view space | |
| var pos = _camera.to_local(_follow_target_bounds.get_endpoint(idx)) | |
| # Take the -ve for z coord since camera faces -ve z. | |
| pos = Vector3(pos.x, pos.y, -pos.z) | |
| # Calculate the 'shadow' cast by the pos on to the projection plane | |
| # using the vertical and horizontal fovs. | |
| var distance_to_proj_plane = projection_plane_depth - pos.z | |
| # Vertical projections | |
| var v_half_span = _tan_vertical_fov * distance_to_proj_plane | |
| var v_max = pos.y + v_half_span | |
| var v_min = pos.y - v_half_span | |
| # Horizontal projections | |
| var h_half_span = _tan_horizontal_fov * distance_to_proj_plane | |
| var h_max = pos.x + h_half_span | |
| var h_min = pos.x - h_half_span | |
| # Update the projection rect to include the max area under the | |
| # collective 'shadows' ie. the outermost projections. | |
| if (v_max > proj_rect_y_max): proj_rect_y_max = v_max; | |
| if (v_min < proj_rect_y_min): proj_rect_y_min = v_min; | |
| if (h_max > proj_rect_x_max): proj_rect_x_max = h_max; | |
| if (h_min < proj_rect_x_min): proj_rect_x_min = h_min; | |
| # Add the rect margin offsets | |
| proj_rect_x_max += margin_offset | |
| proj_rect_x_min -= margin_offset | |
| proj_rect_y_max += margin_offset | |
| proj_rect_y_min -= margin_offset | |
| var desired_depth = max( | |
| # Calculate from center, hence divide by 2. | |
| (proj_rect_y_max - proj_rect_y_min) / (2.0 * _tan_vertical_fov), | |
| (proj_rect_x_max - proj_rect_x_min) / (2.0 * _tan_horizontal_fov) | |
| ) | |
| desired_depth = max(desired_depth, boom_length) | |
| # Calculate the new camera controller position in the view space. | |
| var new_position = Vector3( | |
| # The xy position is the center of the projected rect. | |
| (proj_rect_x_max + proj_rect_x_min) / 2.0, | |
| (proj_rect_y_max + proj_rect_y_min) / 2.0, | |
| # This is the required offset of the camera_controller position | |
| # to make the distance to the projection plane be equal | |
| # to the desired depth. | |
| # Take the -ve since camera faces -ve z. | |
| -(projection_plane_depth - desired_depth) | |
| ) | |
| # Calculate and return the new position in global space. | |
| return _camera.to_global(new_position) | |
| func _precalculate_fov(): | |
| var aspect = get_viewport().size.x / float(get_viewport().size.y) | |
| var half_vertical_fov | |
| var half_horizontal_fov | |
| if _camera.keep_aspect == Camera.KEEP_HEIGHT: | |
| half_vertical_fov = deg2rad(_camera.fov) | |
| half_horizontal_fov = atan(tan(half_vertical_fov) * aspect) | |
| else: | |
| half_horizontal_fov = deg2rad(_camera.fov) | |
| half_vertical_fov = atan(tan(half_horizontal_fov) / aspect) | |
| _tan_vertical_fov = tan(half_vertical_fov) | |
| _tan_horizontal_fov = tan(half_horizontal_fov) | |
| func _update_movement(target: Vector3, delta: float) -> void: | |
| var n1 = ( | |
| _velocity + | |
| (target - _camera.global_transform.origin) * | |
| (move_weight * move_weight) * delta | |
| ) | |
| var n2 = 1 + move_weight * delta | |
| _velocity = n1 / (n2 * n2) | |
| _camera.global_translate(_velocity * delta) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment