diff --git a/maps/waypoint/waypoint.gd b/maps/waypoint/waypoint.gd new file mode 100644 index 0000000..d3f4b33 --- /dev/null +++ b/maps/waypoint/waypoint.gd @@ -0,0 +1,120 @@ +extends Control + +# Some margin to keep the marker away from the screen's corners. +const MARGIN = 8 + +@onready var camera:Camera3D = get_viewport().get_camera_3d() +@onready var parent:Node3D = get_parent() +@onready var label:Label = $Label +@onready var marker:TextureRect = $Marker +# +## The waypoint's text. +@export var text:String: + get: + return $Label.text + set(value): + print("Setting some_exported_property to %s" % value) + $Label.text = value + + +# If `true`, the waypoint sticks to the viewport's edges when moving off-screen. +@export var sticky = true + + +func set_text(p_text:String): + text = p_text + + # The label's text can only be set once the node is ready. + if is_inside_tree(): + label.text = p_text + + +func _ready() -> void: + if not parent is Node3D: + push_error("The waypoint's parent node must inherit from Node3D.") + + +func _process(_delta): + if not camera.is_current(): + # If the camera we have isn't the current one, get the current camera. + camera = get_viewport().get_camera_3d() + + var parent_translation = parent.get_global_transform().origin + var camera_transform = camera.get_global_transform() + var camera_translation = camera_transform.origin + + # We would use "camera.is_position_behind(parent_translation)", except + # that it also accounts for the near clip plane, which we don't want. + var is_behind = camera_transform.basis.z.dot(parent_translation - camera_translation) > 0 + + # Fade the waypoint when the camera gets close. + var distance = camera_translation.distance_to(parent_translation) + modulate.a = clamp(range_lerp(distance, 0, 2, 0, 1), 0, 1 ) + + var unprojected_position = camera.unproject_position(parent_translation) + + var viewport_base_size = get_viewport().size + + if not sticky: + # For non-sticky waypoints, we don't need to clamp and calculate + # the position if the waypoint goes off screen. + rect_position = unprojected_position + visible = not is_behind + return + + # We need to handle the axes differently. + # For the screen's X axis, the projected position is useful to us, + # but we need to force it to the side if it's also behind. + if is_behind: + if unprojected_position.x < viewport_base_size.x / 2: + unprojected_position.x = viewport_base_size.x - MARGIN + else: + unprojected_position.x = MARGIN + + # For the screen's Y axis, the projected position is NOT useful to us + # because we don't want to indicate to the user that they need to look + # up or down to see something behind them. Instead, here we approximate + # the correct position using difference of the X axis Euler angles + # (up/down rotation) and the ratio of that with the camera's FOV. + # This will be slightly off from the theoretical "ideal" position. + if is_behind or unprojected_position.x < MARGIN or \ + unprojected_position.x > viewport_base_size.x - MARGIN: + var look = camera_transform.looking_at(parent_translation, Vector3.UP) + var diff = angle_diff(look.basis.get_euler().x, camera_transform.basis.get_euler().x) + unprojected_position.y = viewport_base_size.y * (0.5 + (diff / deg2rad(camera.fov))) + + rect_position = Vector2( + clamp(unprojected_position.x, MARGIN, viewport_base_size.x - MARGIN), + clamp(unprojected_position.y, MARGIN, viewport_base_size.y - MARGIN) + ) + + label.visible = true + rect_rotation = 0 + # Used to display a diagonal arrow when the waypoint is displayed in + # one of the screen corners. + var overflow = 0 + + if rect_position.x <= MARGIN: + # Left overflow. + overflow = -45 + label.visible = false + rect_rotation = 90 + elif rect_position.x >= viewport_base_size.x - MARGIN: + # Right overflow. + overflow = 45 + label.visible = false + rect_rotation = 270 + + if rect_position.y <= MARGIN: + # Top overflow. + label.visible = false + rect_rotation = 180 + overflow + elif rect_position.y >= viewport_base_size.y - MARGIN: + # Bottom overflow. + label.visible = false + rect_rotation = -overflow + + +static func angle_diff(from, to): + var diff = fmod(to - from, TAU) + return fmod(2.0 * diff, TAU) - diff diff --git a/maps/waypoint/waypoint.svg b/maps/waypoint/waypoint.svg new file mode 100644 index 0000000..0a3bb69 --- /dev/null +++ b/maps/waypoint/waypoint.svg @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/maps/waypoint/waypoint.svg.import b/maps/waypoint/waypoint.svg.import new file mode 100644 index 0000000..87145a8 --- /dev/null +++ b/maps/waypoint/waypoint.svg.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture2D" +uid="uid://ltyiyj3v77cn" +path="res://.godot/imported/waypoint.svg-cce94092b899bcae1a4b555f72b67ac0.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://maps/waypoint/waypoint.svg" +dest_files=["res://.godot/imported/waypoint.svg-cce94092b899bcae1a4b555f72b67ac0.stex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +compress/streamed=false +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/HDR_as_SRGB=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 diff --git a/maps/waypoint/waypoint.tscn b/maps/waypoint/waypoint.tscn new file mode 100644 index 0000000..4507059 --- /dev/null +++ b/maps/waypoint/waypoint.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=3 format=3 uid="uid://c7sga2ipka52x"] + +[ext_resource type="Script" path="res://maps/waypoint/waypoint.gd" id="1_usbk5"] +[ext_resource type="Texture2D" uid="uid://ltyiyj3v77cn" path="res://maps/waypoint/waypoint.svg" id="2_hvndi"] + +[node name="Control" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( "1_usbk5" ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="."] +offset_left = -200.0 +offset_top = -46.0 +offset_right = 200.0 +offset_bottom = -20.0 +text = "Waypoint" +horizontal_alignment = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Marker" type="TextureRect" parent="."] +offset_left = -7.3 +offset_top = -23.0 +offset_right = 202.7 +offset_bottom = 274.0 +rect_scale = Vector2(0.07, 0.08) +size_flags_horizontal = 0 +size_flags_vertical = 0 +texture = ExtResource( "2_hvndi" ) +__meta__ = { +"_edit_use_anchors_": false +}