@tool extends EditorNode3DGizmoPlugin const ONE_VEC = Vector3(1.0, 1.0, 1.0) const MAX_DEFLECTION = 4096.0 var undo_redo: EditorUndoRedoManager var handles: PackedVector3Array = [ Vector3.UP, -Vector3.FORWARD, Vector3.RIGHT, -Vector3.UP, Vector3.FORWARD, -Vector3.RIGHT ] var directions: Array[String] = [ "+Y", "+Z", "+X", "-Y", "-Z", "-X" ] var visible = true var first_pos: Variant var first_scale: Vector3 var first_position: Vector3 var first_global: Vector3 func _has_gizmo(node): return node is Node3D func _get_gizmo_name() -> String: return "Rodot Resize" func _init(undo_redo: EditorUndoRedoManager): self.undo_redo = undo_redo create_handle_material("handles") func _redraw(gizmo: EditorNode3DGizmo): gizmo.clear() if visible: gizmo.add_handles(handles, get_material("handles", gizmo), []) func _get_handle_name(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> String: return "Resize " + directions[handle_id] func _get_handle_value(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> Variant: return handles[handle_id] func _begin_handle_action(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> void: var node = gizmo.get_node_3d() first_pos = null first_scale = node.scale first_position = node.position first_global = node.global_position func _commit_handle(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool, restore: Variant, cancel: bool) -> void: var node = gizmo.get_node_3d() if cancel: node.scale = first_scale node.position = first_position return if node.scale.is_equal_approx(first_scale) and node.position.is_equal_approx(first_position): return undo_redo.create_action("Resize Node3D") undo_redo.add_do_property(node, "position", node.position) undo_redo.add_undo_property(node, "position", first_position) undo_redo.add_do_property(node, "scale", node.scale) undo_redo.add_undo_property(node, "scale", first_scale) undo_redo.commit_action() func _update_node(node: Node3D, change: Vector3, axis: Vector3, minimum_deflection = 1.0) -> void: # Reset to default node.position = first_position node.scale = first_scale if change.length() == 0: # No size change return var axis_direction = sign(change.dot(axis)) var direction = axis_direction * sign(axis.dot(axis.abs())) var amount_to_add = change.length() * direction var current_scale = (first_scale * axis.abs()).length() # If user attempts to go negative or zero, force them back to the minimum amount var deflection = current_scale + amount_to_add if deflection < minimum_deflection: amount_to_add = amount_to_add - deflection + minimum_deflection var abs_axis: Vector3 = axis.abs() var scale_vec: Vector3 = ONE_VEC - abs_axis var factor: float = (current_scale + amount_to_add) / current_scale var axis_factor: Vector3 = abs_axis * factor var apply_scale: Vector3 = scale_vec + axis_factor var pos_factor = ((apply_scale - ONE_VEC) / 2) * axis node.translate_object_local(pos_factor) node.scale_object_local(apply_scale) func _set_handle(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool, camera: Camera3D, screen_pos: Vector2) -> void: var node := gizmo.get_node_3d() var pos = _get_projected_pos(gizmo, handle_id, camera, screen_pos) if not pos is Vector3: return if not first_pos: first_pos = pos return var o_pos := first_pos as Vector3 var m_pos := pos as Vector3 var axis := handles[handle_id] var offset := (m_pos - o_pos) * axis var scale_snap: float = EditorInterface.get_node_3d_scale_snap() / 100.0 var handle_diff := offset.length() if is_nan(handle_diff) or is_inf(handle_diff) or handle_diff >= MAX_DEFLECTION: return var snap_diff = handle_diff if EditorInterface.is_node_3d_snap_enabled(): snap_diff = roundf(handle_diff / scale_snap) * scale_snap var handle_dot := offset.normalized().dot(axis) var handle_dir = sign(handle_dot) var handle_diff_vec = axis * snap_diff * handle_dir _update_node(node, handle_diff_vec, axis, scale_snap) node.update_gizmos() func _get_projected_pos(gizmo: EditorNode3DGizmo, handle_id: int, camera: Camera3D, screen_pos: Vector2) -> Variant: var node := gizmo.get_node_3d() var from := camera.project_ray_origin(screen_pos) var plane = Plane(from, first_global) var pos = plane.intersects_ray(from, camera.project_ray_normal(screen_pos) * MAX_DEFLECTION) return pos