Skip to content

Instantly share code, notes, and snippets.

@PranavSK
Created March 24, 2021 05:09
Show Gist options
  • Select an option

  • Save PranavSK/59fe9ec5f6f6a137313c8a5dd0df7260 to your computer and use it in GitHub Desktop.

Select an option

Save PranavSK/59fe9ec5f6f6a137313c8a5dd0df7260 to your computer and use it in GitHub Desktop.
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