/
gui_3d.gd
130 lines (96 loc) · 4.78 KB
/
gui_3d.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
extends Node3D
# Used for checking if the mouse is inside the Area3D.
var is_mouse_inside = false
# The last processed input touch/mouse event. To calculate relative movement.
var last_event_pos2D = null
# The time of the last event in seconds since engine start.
var last_event_time: float = -1.0
@onready var node_viewport = $SubViewport
@onready var node_quad = $Quad
@onready var node_area = $Quad/Area3D
func _ready():
node_area.mouse_entered.connect(_mouse_entered_area)
node_area.mouse_exited.connect(_mouse_exited_area)
node_area.input_event.connect(_mouse_input_event)
# If the material is NOT set to use billboard settings, then avoid running billboard specific code
if node_quad.get_surface_override_material(0).billboard_mode == BaseMaterial3D.BillboardMode.BILLBOARD_DISABLED:
set_process(false)
func _process(_delta):
# NOTE: Remove this function if you don't plan on using billboard settings.
rotate_area_to_billboard()
func _mouse_entered_area():
is_mouse_inside = true
func _mouse_exited_area():
is_mouse_inside = false
func _unhandled_input(event):
# Check if the event is a non-mouse/non-touch event
for mouse_event in [InputEventMouseButton, InputEventMouseMotion, InputEventScreenDrag, InputEventScreenTouch]:
if is_instance_of(event, mouse_event):
# If the event is a mouse/touch event, then we can ignore it here, because it will be
# handled via Physics Picking.
return
node_viewport.push_input(event)
func _mouse_input_event(_camera: Camera3D, event: InputEvent, event_position: Vector3, _normal: Vector3, _shape_idx: int):
# Get mesh size to detect edges and make conversions. This code only support PlaneMesh and QuadMesh.
var quad_mesh_size = node_quad.mesh.size
# Event position in Area3D in world coordinate space.
var event_pos3D = event_position
# Current time in seconds since engine start.
var now: float = Time.get_ticks_msec() / 1000.0
# Convert position to a coordinate space relative to the Area3D node.
# NOTE: affine_inverse accounts for the Area3D node's scale, rotation, and position in the scene!
event_pos3D = node_quad.global_transform.affine_inverse() * event_pos3D
# TODO: Adapt to bilboard mode or avoid completely.
var event_pos2D: Vector2 = Vector2()
if is_mouse_inside:
# Convert the relative event position from 3D to 2D.
event_pos2D = Vector2(event_pos3D.x, -event_pos3D.y)
# Right now the event position's range is the following: (-quad_size/2) -> (quad_size/2)
# We need to convert it into the following range: -0.5 -> 0.5
event_pos2D.x = event_pos2D.x / quad_mesh_size.x
event_pos2D.y = event_pos2D.y / quad_mesh_size.y
# Then we need to convert it into the following range: 0 -> 1
event_pos2D.x += 0.5
event_pos2D.y += 0.5
# Finally, we convert the position to the following range: 0 -> viewport.size
event_pos2D.x *= node_viewport.size.x
event_pos2D.y *= node_viewport.size.y
# We need to do these conversions so the event's position is in the viewport's coordinate system.
elif last_event_pos2D != null:
# Fall back to the last known event position.
event_pos2D = last_event_pos2D
# Set the event's position and global position.
event.position = event_pos2D
if event is InputEventMouse:
event.global_position = event_pos2D
# Calculate the relative event distance.
if event is InputEventMouseMotion or event is InputEventScreenDrag:
# If there is not a stored previous position, then we'll assume there is no relative motion.
if last_event_pos2D == null:
event.relative = Vector2(0, 0)
# If there is a stored previous position, then we'll calculate the relative position by subtracting
# the previous position from the new position. This will give us the distance the event traveled from prev_pos.
else:
event.relative = event_pos2D - last_event_pos2D
event.velocity = event.relative / (now - last_event_time)
# Update last_event_pos2D with the position we just calculated.
last_event_pos2D = event_pos2D
# Update last_event_time to current time.
last_event_time = now
# Finally, send the processed input event to the viewport.
node_viewport.push_input(event)
func rotate_area_to_billboard():
var billboard_mode = node_quad.get_surface_override_material(0).billboard_mode
# Try to match the area with the material's billboard setting, if enabled.
if billboard_mode > 0:
# Get the camera.
var camera = get_viewport().get_camera_3d()
# Look in the same direction as the camera.
var look = camera.to_global(Vector3(0, 0, -100)) - camera.global_transform.origin
look = node_area.position + look
# Y-Billboard: Lock Y rotation, but gives bad results if the camera is tilted.
if billboard_mode == 2:
look = Vector3(look.x, 0, look.z)
node_area.look_at(look, Vector3.UP)
# Rotate in the Z axis to compensate camera tilt.
node_area.rotate_object_local(Vector3.BACK, camera.rotation.z)