| 1 | @tool
|
| 2 | extends EditorNode3DGizmoPlugin
|
| 3 |
|
| 4 | const ONE_VEC = Vector3(1.0, 1.0, 1.0)
|
| 5 | const MAX_DEFLECTION = 4096.0
|
| 6 |
|
| 7 | var undo_redo: EditorUndoRedoManager
|
| 8 |
|
| 9 | var handles: PackedVector3Array = [
|
| 10 | Vector3.UP,
|
| 11 | -Vector3.FORWARD,
|
| 12 | Vector3.RIGHT,
|
| 13 |
|
| 14 | -Vector3.UP,
|
| 15 | Vector3.FORWARD,
|
| 16 | -Vector3.RIGHT
|
| 17 | ]
|
| 18 |
|
| 19 | var directions: Array[String] = [
|
| 20 | "+Y", "+Z", "+X", "-Y", "-Z", "-X"
|
| 21 | ]
|
| 22 |
|
| 23 | var visible = true
|
| 24 |
|
| 25 | var first_pos: Variant
|
| 26 | var first_scale: Vector3
|
| 27 | var first_position: Vector3
|
| 28 | var first_global: Vector3
|
| 29 |
|
| 30 | func _has_gizmo(node):
|
| 31 | return node is Node3D
|
| 32 |
|
| 33 | func _get_gizmo_name() -> String:
|
| 34 | return "Rodot Resize"
|
| 35 |
|
| 36 | func _init(undo_redo: EditorUndoRedoManager):
|
| 37 | self.undo_redo = undo_redo
|
| 38 | create_handle_material("handles")
|
| 39 |
|
| 40 | func _redraw(gizmo: EditorNode3DGizmo):
|
| 41 | gizmo.clear()
|
| 42 | if visible:
|
| 43 | gizmo.add_handles(handles, get_material("handles", gizmo), [])
|
| 44 |
|
| 45 | func _get_handle_name(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> String:
|
| 46 | return "Resize " + directions[handle_id]
|
| 47 |
|
| 48 | func _get_handle_value(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> Variant:
|
| 49 | return handles[handle_id]
|
| 50 |
|
| 51 | func _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 |
|
| 58 | func _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 |
|
| 75 | func _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 |
|
| 105 | func _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 |
|
| 142 | func _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 |
|