Swarm/Misc/RTSCamera3D.gd

176 lines
7.3 KiB
GDScript3
Raw Normal View History

extends Camera3D
class_name RTSCamera3D
@export var dragging_enabled: bool = true
@export var panning_enabled: bool = true
@export var edge_panning_enabled: bool = true
@export var rotating_enabled: bool = true
@export var zooming_enabled: bool = true
@export var dragging_intersect_plane_normal: Vector3 = Vector3.UP
@export var dragging_intersect_plane_distance: float = 0.0
@export_range(0.0, 1000.0, 10.0) var pan_speed: float = 20.0
@export_range(0.0, 1000.0, 10.0) var edge_pan_speed: float = 20.0
@export_range(0, 1.0, 0.01) var edge_trigger_percentage: float = 0.005
@export var lock_onto_position: bool = false
@export var lockon_position: Vector3 = Vector3.ZERO
@export var rotate_min_angle: float = 15.0
@export var rotate_max_angle: float = 80.0
@export var min_camera_distance: float = 1.0
@export var max_camera_distance: float = 50.0
@export var bounding_box: Vector2 = Vector2(100.0, 100.0)
@export var world_center: Vector2 = Vector2.ZERO
var dragging: bool = false
var drag_cam_position: Vector3 = Vector3.ZERO
var drag_2d_position: Vector2 = Vector2.ZERO
var rotating: bool = false
var orbit_angles: Vector2 = Vector2.ZERO #(deg_to_rad(-60.0), 0.0)
var temp_orbit_angles: Vector2 = Vector2.ZERO
var orbit_distance: float = 20.0
var orbitPosition: Vector3 = Vector3.ZERO
var rotate_3d_position: Vector3 = Vector3.ZERO
var rotate_2d_position: Vector2 = Vector2.ZERO
var rotate_2d_position_old: Vector2 = Vector2.ZERO
var panning_move_vector: Vector3 = Vector3.ZERO
var edge_panning_move_vector: Vector3 = Vector3.ZERO
var zoom_3d_position: Vector3 = Vector3.ZERO
var camera_distance: float = 0.0
var camera_distance_old: float = 0.0
var _camera_distance: float = 30.0
# TODO: Fix look_at() errors
func _ready():
await get_tree().process_frame
#set correct camera distance
rotate_3d_position = get_3d_pos(get_viewport().size / 2.0)
look_at(lockon_position)
if lock_onto_position:
rotate_3d_position = lockon_position
camera_distance = rotate_3d_position.distance_to(global_position)
camera_distance_old = camera_distance
orbit_angles = Vector2(global_rotation.x, global_rotation.y)
# This is to prevent edge_panning from panning when
# the mouse never entered the viewport before
var mouse_has_entered_once: bool = false
func _set_zoom_level(value: float) -> void:
_camera_distance = clamp(value, min_camera_distance, max_camera_distance)
var tween = get_tree().create_tween()
tween.tween_property(
self,
"camera_distance",
_camera_distance,
0.3
)
func _unhandled_input(event: InputEvent) -> void:
var pos_3d = get_3d_pos(get_viewport().get_mouse_position())
if pos_3d:
var pos_3d_rounded = "%.2f, %.2f, %.2f" % [pos_3d.x, pos_3d.y, pos_3d.z]
if event.is_action_pressed("drag_camera") and not rotating and not lock_onto_position:
_camera_distance = camera_distance
drag_2d_position = get_viewport().get_mouse_position()
drag_cam_position = position
dragging = true
elif event.is_action_released("drag_camera"):
camera_distance = _camera_distance
_camera_distance = 0
dragging = false
if event.is_action_pressed("rotate_camera") and not dragging:
rotate_3d_position = get_3d_pos(get_viewport().size / 2.0)
if lock_onto_position:
look_at(lockon_position)
rotate_3d_position = lockon_position
rotate_2d_position = get_viewport().get_mouse_position()
rotate_2d_position_old = get_viewport().get_mouse_position()
temp_orbit_angles = orbit_angles
rotating = true
elif event.is_action_released("rotate_camera"):
orbit_angles = temp_orbit_angles
rotating = false
if zooming_enabled:
if event.is_action_pressed("zoom_camera_in") and not dragging:
_set_zoom_level(camera_distance - 1)
zoom_3d_position = get_3d_pos(get_viewport().size / 2.0)
elif event.is_action_pressed("zoom_camera_out") and not dragging:
_set_zoom_level(camera_distance + 1)
zoom_3d_position = get_3d_pos(get_viewport().size / 2.0)
func get_3d_pos(position2D: Vector2):
return Plane(dragging_intersect_plane_normal, dragging_intersect_plane_distance).intersects_ray(project_ray_origin(position2D), project_ray_normal(position2D))
func _notification(what: int) -> void:
match what:
NOTIFICATION_WM_MOUSE_ENTER:
mouse_has_entered_once = true
func _process(delta: float) -> void:
panning_move_vector = Vector3.ZERO
edge_panning_move_vector = Vector3.ZERO
var mouse_position: Vector2 = get_viewport().get_mouse_position()
if panning_enabled and not lock_onto_position:
var camera_move_vector = Vector2(Input.get_action_strength("camera_right") - Input.get_action_strength("camera_left"), Input.get_action_strength("camera_down") - Input.get_action_strength("camera_up"))
panning_move_vector = Vector3(camera_move_vector.x, 0.0, camera_move_vector.y).rotated(Vector3.UP, rotation.y)
if edge_panning_enabled and mouse_has_entered_once and not lock_onto_position:
var percentage = mouse_position / Vector2(get_viewport().size)
if percentage.x <= edge_trigger_percentage:
edge_panning_move_vector = Vector3(-1, 0.0, 0.0).rotated(Vector3.UP, rotation.y)
elif percentage.x >= 1.0 - edge_trigger_percentage:
edge_panning_move_vector = Vector3(1, 0.0, 0.0).rotated(Vector3.UP, rotation.y)
elif percentage.y <= edge_trigger_percentage:
edge_panning_move_vector = Vector3(0.0, 0.0, -1).rotated(Vector3.UP, rotation.y)
elif percentage.y >= 1.0 - edge_trigger_percentage:
edge_panning_move_vector = Vector3(0.0, 0.0, 1).rotated(Vector3.UP, rotation.y)
# TODO: When reaching the limits, reset the rotation diff
# Currently: If you rotate up/down and go out of limits, you have to move back all the overshoot
# to move the camera back again
if rotating_enabled and rotating and not dragging:
rotate_2d_position = get_viewport().get_mouse_position()
var mouse_diff = rotate_2d_position - rotate_2d_position_old
temp_orbit_angles = Vector2(orbit_angles.x - mouse_diff.y * 0.01, orbit_angles.y - mouse_diff.x * 0.01)
temp_orbit_angles.x = clamp(temp_orbit_angles.x, -deg_to_rad(rotate_max_angle), -deg_to_rad(rotate_min_angle))
var lookRotation = Quaternion.from_euler(Vector3(temp_orbit_angles.x, temp_orbit_angles.y, 0.0))
var lookDirection = lookRotation * Vector3.FORWARD
var lookPosition = rotate_3d_position - lookDirection * camera_distance
look_at_from_position(lookPosition, rotate_3d_position)
if not is_equal_approx(camera_distance, camera_distance_old) and not rotating:
var lookRotation = Quaternion.from_euler(Vector3(orbit_angles.x, orbit_angles.y, 0.0))
var lookDirection = lookRotation * Vector3.FORWARD
var lookPosition = rotate_3d_position - lookDirection * camera_distance
look_at_from_position(lookPosition, rotate_3d_position)
camera_distance_old = camera_distance
# TODO: Always apply zooming and figure out how to reset initial drag position
# Probably just "stop" and start a new drag when zoom is different?
# Or always stop/start a new drag?
if dragging_enabled and dragging and not rotating and not lock_onto_position:
var new_3d_pos = get_3d_pos(mouse_position)
if new_3d_pos:
var drag_world_position = get_3d_pos(drag_2d_position)
if drag_world_position:
var mouse_diff = drag_world_position - new_3d_pos
var new_cam_pos = drag_cam_position + mouse_diff
position = new_cam_pos
rotate_3d_position = get_3d_pos(get_viewport().size / 2.0)
else:
position += ((panning_move_vector * pan_speed) + (edge_panning_move_vector * edge_pan_speed)) * delta