Last active 4 days ago

gizmo.gd Raw
1@tool
2extends EditorNode3DGizmoPlugin
3
4const ONE_VEC = Vector3(1.0, 1.0, 1.0)
5const MAX_DEFLECTION = 4096.0
6
7var undo_redo: EditorUndoRedoManager
8
9var handles: PackedVector3Array = [
10 Vector3.UP,
11 -Vector3.FORWARD,
12 Vector3.RIGHT,
13
14 -Vector3.UP,
15 Vector3.FORWARD,
16 -Vector3.RIGHT
17]
18
19var directions: Array[String] = [
20 "+Y", "+Z", "+X", "-Y", "-Z", "-X"
21]
22
23var visible = true
24
25var first_pos: Variant
26var first_scale: Vector3
27var first_position: Vector3
28var first_global: Vector3
29
30func _has_gizmo(node):
31 return node is Node3D
32
33func _get_gizmo_name() -> String:
34 return "Rodot Resize"
35
36func _init(undo_redo: EditorUndoRedoManager):
37 self.undo_redo = undo_redo
38 create_handle_material("handles")
39
40func _redraw(gizmo: EditorNode3DGizmo):
41 gizmo.clear()
42 if visible:
43 gizmo.add_handles(handles, get_material("handles", gizmo), [])
44
45func _get_handle_name(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> String:
46 return "Resize " + directions[handle_id]
47
48func _get_handle_value(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> Variant:
49 return handles[handle_id]
50
51func _begin_handle_action(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> void:
52 var node = gizmo.get_node_3d()
53 first_pos = null
54 first_scale = node.scale
55 first_position = node.position
56 first_global = node.global_position
57
58func _commit_handle(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool, restore: Variant, cancel: bool) -> void:
59 var node = gizmo.get_node_3d()
60 if cancel:
61 node.scale = first_scale
62 node.position = first_position
63 return
64
65 if node.scale.is_equal_approx(first_scale) and node.position.is_equal_approx(first_position):
66 return
67
68 undo_redo.create_action("Resize Node3D")
69 undo_redo.add_do_property(node, "position", node.position)
70 undo_redo.add_undo_property(node, "position", first_position)
71 undo_redo.add_do_property(node, "scale", node.scale)
72 undo_redo.add_undo_property(node, "scale", first_scale)
73 undo_redo.commit_action()
74
75func _update_node(node: Node3D, change: Vector3, axis: Vector3, minimum_deflection = 1.0) -> void:
76 # Reset to default
77 node.position = first_position
78 node.scale = first_scale
79
80 if change.length() == 0:
81 # No size change
82 return
83
84 var axis_direction = sign(change.dot(axis))
85 var direction = axis_direction * sign(axis.dot(axis.abs()))
86 var amount_to_add = change.length() * direction
87 var current_scale = (first_scale * axis.abs()).length()
88
89 # If user attempts to go negative or zero, force them back to the minimum amount
90 var deflection = current_scale + amount_to_add
91 if deflection < minimum_deflection:
92 amount_to_add = amount_to_add - deflection + minimum_deflection
93
94 var abs_axis: Vector3 = axis.abs()
95 var scale_vec: Vector3 = ONE_VEC - abs_axis
96 var factor: float = (current_scale + amount_to_add) / current_scale
97 var axis_factor: Vector3 = abs_axis * factor
98 var apply_scale: Vector3 = scale_vec + axis_factor
99
100 var pos_factor = ((apply_scale - ONE_VEC) / 2) * axis
101
102 node.translate_object_local(pos_factor)
103 node.scale_object_local(apply_scale)
104
105func _set_handle(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool, camera: Camera3D, screen_pos: Vector2) -> void:
106 var node := gizmo.get_node_3d()
107 var pos = _get_projected_pos(gizmo, handle_id, camera, screen_pos)
108
109 if not pos is Vector3:
110 return
111
112 if not first_pos:
113 first_pos = pos
114 return
115
116 var o_pos := first_pos as Vector3
117 var m_pos := pos as Vector3
118 var axis := handles[handle_id]
119
120 var offset := (m_pos - o_pos) * axis
121
122 var scale_snap: float = EditorInterface.get_node_3d_scale_snap() / 100.0
123
124 var handle_diff := offset.length()
125 if is_nan(handle_diff) or is_inf(handle_diff) or handle_diff >= MAX_DEFLECTION:
126 return
127
128 var snap_diff = handle_diff
129
130 if EditorInterface.is_node_3d_snap_enabled():
131 snap_diff = roundf(handle_diff / scale_snap) * scale_snap
132
133 var handle_dot := offset.normalized().dot(axis)
134 var handle_dir = sign(handle_dot)
135
136 var handle_diff_vec = axis * snap_diff * handle_dir
137
138 _update_node(node, handle_diff_vec, axis, scale_snap)
139
140 node.update_gizmos()
141
142func _get_projected_pos(gizmo: EditorNode3DGizmo, handle_id: int, camera: Camera3D, screen_pos: Vector2) -> Variant:
143 var node := gizmo.get_node_3d()
144 var from := camera.project_ray_origin(screen_pos)
145
146 var plane = Plane(from, first_global)
147 var pos = plane.intersects_ray(from, camera.project_ray_normal(screen_pos) * MAX_DEFLECTION)
148
149 return pos
150